add java code

pull/128/head
Anton Romanov 6 years ago
parent 6d84626599
commit 671cfdd486

@ -0,0 +1,127 @@
buildscript {
ext {
versionSpringBoot = '1.5.10.RELEASE'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath group: 'org.springframework.boot', name: 'spring-boot-gradle-plugin', version: versionSpringBoot
}
}
group 'ru.ulstu'
version '0.1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'checkstyle'
build.dependsOn checkstyleMain
bootRun.dependsOn checkstyleMain
sourceCompatibility = 1.8
targetCompatibility = 1.8
checkstyle {
project.ext.checkstyleVersion = '8.8'
project.ext.sevntuChecksVersion = '1.28.0'
repositories {
mavenLocal()
mavenCentral()
}
ignoreFailures = false
showViolations = true
maxErrors = 1
maxWarnings = 1
configFile = file("${project.rootDir}/checkstyle.xml")
//sourceSets = [sourceSets.main]
//showViolations = true
//reportsDir = file("$project.buildDir/checkstyleReports")
//configProperties = ['baseDir': "$project.projectDir"]
//https://discuss.gradle.org/t/some-checkstyle-rules-dont-work-in-gradle/16102/4
checkstyleMain {
source = sourceSets.main.allSource
}
configurations {
checkstyle
}
dependencies{
assert project.hasProperty("checkstyleVersion")
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
checkstyle "com.github.sevntu-checkstyle:sevntu-checks:${sevntuChecksVersion}"
}
}
task health(dependsOn: [
'checkstyleMain'
])
jar {
baseName = 'ng-tracker'
}
compileJava {
options.encoding = "UTF-8"
}
repositories {
mavenLocal()
mavenCentral()
}
configurations {
compile.exclude module: "spring-boot-starter-tomcat"
compile.exclude module: "bcmail-jdk14"
compile.exclude module: "bcprov-jdk14"
compile.exclude module: "bctsp-jdk14"
}
dependencies {
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-security'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-mail'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-jetty'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf'
compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity4'
compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-afterburner'
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-hibernate5'
compile group: 'postgresql', name: 'postgresql', version: '9.1-901.jdbc4'
compile group: 'org.liquibase', name: 'liquibase-core', version: '3.5.3'
compile group: 'com.mattbertolini', name: 'liquibase-slf4j', version: '2.0.0'
compile group: 'org.apache.poi', name: 'poi', version: '3.9'
compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.9'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.7'
compile group: 'com.lowagie', name: 'itext', version: '2.1.7'
compile group: 'org.webjars', name: 'bootstrap', version: '3.3.7-1'
compile group: 'org.webjars', name: 'bootstrap-select', version: '1.12.4'
compile group: 'org.webjars', name: 'jquery', version: '3.3.1-1'
compile group: 'org.webjars', name: 'font-awesome', version: '4.7.0'
compile group: 'org.webjars', name: 'jstree', version: '3.3.3'
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.5.0'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.5.0'
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test'
}

@ -0,0 +1,172 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the sun coding conventions from:
- the Java Language Specification at
http://java.sun.com/docs/books/jls/second_edition/html/index.html
- the Sun Code Conventions at http://java.sun.com/docs/codeconv/
- the Javadoc guidelines at
http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
- the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
- some best practices
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sf.net (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
Finally, it is worth reading the documentation.
-->
<module name="Checker">
<!--
If you set the basedir property below, then all reported file
names will be relative to the specified directory. See
http://checkstyle.sourceforge.net/5.x/config.html#Checker
<property name="basedir" value="${basedir}"/>
-->
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Checks that a package-info.java file exists for each package. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage -->
<!--<module name="JavadocPackage"/>-->
<!-- Checks whether files end with a new line. -->
<!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
<!--<module name="NewlineAtEndOfFile"/>-->
<!-- Checks that property files contain the same keys. -->
<!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
<module name="Translation"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="FileLength"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<!--<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>-->
<!-- Checks for Headers -->
<!-- See http://checkstyle.sf.net/config_header.html -->
<!-- <module name="Header"> -->
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
<!-- <property name="fileExtensions" value="java"/> -->
<!-- </module> -->
<module name="TreeWalker">
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<!--<module name="JavadocMethod"/>-->
<!--<module name="JavadocType"/>-->
<!--<module name="JavadocVariable"/>-->
<!--<module name="JavadocStyle"/>-->
<!-- Checks for Naming Conventions. -->
<!-- See http://checkstyle.sf.net/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="MemberName"/>
<module name="MethodName"/>
<module name="PackageName"/>
<module name="ParameterName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See http://checkstyle.sf.net/config_import.html -->
<!--<module name="AvoidStarImport"/>-->
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<!--module name="UnusedImports">
<property name="processJavadoc" value="false"/>
</module-->
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<!--<module name="LineLength"/>-->
<module name="MethodLength"/>
<!--<module name="ParameterNumber"/>-->
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="EmptyForIteratorPad"/>
<!--<module name="GenericWhitespace"/>-->
<!--<module name="MethodParamPad"/>-->
<!--<module name="NoWhitespaceAfter"/>-->
<module name="NoWhitespaceBefore"/>
<!--<module name="OperatorWrap"/>-->
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<!--<module name="WhitespaceAfter"/>-->
<module name="WhitespaceAround"/>
<!-- Modifier Checks -->
<!-- See http://checkstyle.sf.net/config_modifiers.html -->
<!--<module name="ModifierOrder"/>-->
<module name="RedundantModifier"/>
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sf.net/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock"/>
<module name="LeftCurly"/>
<!--<module name="NeedBraces"/>-->
<module name="RightCurly"/>
<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sf.net/config_coding.html -->
<!--<module name="AvoidInlineConditionals"/>-->
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<!--<module name="HiddenField"/>-->
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<!--<module name="MagicNumber"/>-->
<!--<module name="MissingSwitchDefault"/>-->
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<!-- Checks for class design -->
<!-- See http://checkstyle.sf.net/config_design.html -->
<!--<module name="DesignForExtension"/>-->
<!--<module name="FinalClass"/>-->
<!--<module name="HideUtilityClassConstructor"/>-->
<!--<module name="InterfaceIsType"/>-->
<!--<module name="VisibilityModifier"/>-->
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<module name="ArrayTypeStyle"/>
<!--<module name="FinalParameters"/>-->
<!--<module name="TodoComment"/>-->
<!--<module name="UpperEll"/>-->
</module>
</module>

Binary file not shown.

@ -0,0 +1,6 @@
#Tue Feb 06 11:48:53 SAMT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip

169
gradlew vendored

@ -0,0 +1,169 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
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"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

84
gradlew.bat vendored

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -1,112 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>NG-Tacker</title>
<!-- Bootstrap core CSS -->
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom fonts for this template -->
<link href="vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="vendor/google/montserrat.css" rel="stylesheet" type="text/css">
<link href='vendor/google/kaushan.css' rel='stylesheet' type='text/css'>
<link href='vendor/google/droid.css' rel='stylesheet' type='text/css'>
<link href='vendor/google/roboto.css' rel='stylesheet' type='text/css'>
<!-- Custom styles for this template -->
<link href="css/agency.css" rel="stylesheet">
</head>
<body id="page-top">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark fixed-top navbar-shrink" id="mainNav">
<div class="container">
<a class="navbar-brand js-scroll-trigger" href="#page-top">NG-Tracker</a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false"
aria-label="Toggle navigation">
Menu
<i class="fa fa-bars"></i>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav text-uppercase ml-auto">
<li class="nav-item">
<a class="nav-link js-scroll-trigger" target="_blank" href="#landing">НИО-17</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" target="_blank" href="http://is.ulstu.ru">Сайт кафедры</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" target="_blank" href="https://kias.rfbr.ru/">КИАС РФФИ</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#logout">Выход</a>
</li>
</ul>
</div>
</div>
</nav>
<section id="services">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<h2 class="section-heading text-uppercase">Статьи</h2>
</div>
</div>
<div class="row text-left">
<div class="col-md-12">
<span class="fa-stack fa-1x">
<i class="fa fa-circle fa-stack-2x text-warning"></i>
<i class="fa fa-file-text-o fa-stack-1x fa-inverse"></i>
</span>
<a href="paper.html?id=1"><span>А.А. Романов, И.А. Тимина. Обзор методов оценки трудоемкости производства</span></a>
</div>
</div>
<div class="row text-left">
<div class="col-md-12">
<span class="fa-stack fa-1x">
<i class="fa fa-circle fa-stack-2x text-primary"></i>
<i class="fa fa-file-text-o fa-stack-1x fa-inverse"></i>
</span>
<a href="paper.html?id=2"><span>А.А. Филиппов, В.С.Мошкин, Н.Г. Ярушкина. Построение онтологии предметной ...</span></a>
</div>
</div>
<div class="row text-left">
<div class="col-md-12">
<span class="fa-stack fa-1x">
<i class="fa fa-circle fa-stack-2x text-success"></i>
<i class="fa fa-file-text-o fa-stack-1x fa-inverse"></i>
</span>
<a href="paper.html?id=2"><span>А.А. Филиппов, В.С.Мошкин, Н.Г. Ярушкина. Построение онтологии предметной ...</span></a>
</div>
</div>
</div>
</section>
<!-- Bootstrap core JavaScript -->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Plugin JavaScript -->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Contact form JavaScript -->
<script src="js/jqBootstrapValidation.js"></script>
<script src="js/contact_me.js"></script>
<!-- Custom scripts for this template -->
<script src="js/agency.js"></script>
</body>
</html>

@ -0,0 +1,14 @@
package ru.ulstu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import ru.ulstu.core.repository.JpaDetachableRepositoryImpl;
@SpringBootApplication
@EnableJpaRepositories(repositoryBaseClass = JpaDetachableRepositoryImpl.class)
public class NgTrackerApplication {
public static void main(String[] args) {
SpringApplication.run(NgTrackerApplication.class, args);
}
}

@ -0,0 +1,33 @@
package ru.ulstu.commit.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ru.ulstu.configuration.Constants;
import ru.ulstu.core.model.response.PageableItems;
import ru.ulstu.commit.model.CommitListDto;
import ru.ulstu.commit.service.CommitService;
import ru.ulstu.core.model.response.Response;
import ru.ulstu.odin.controller.OdinController;
import ru.ulstu.odin.model.OdinVoid;
import static ru.ulstu.commit.controller.CommitController.URL;
@RestController
@RequestMapping(URL)
public class CommitController extends OdinController<CommitListDto, OdinVoid> {
public static final String URL = Constants.API_1_0 + "commits";
private final CommitService commitService;
public CommitController(CommitService commitService) {
super(CommitListDto.class);
this.commitService = commitService;
}
@GetMapping("")
public Response<PageableItems<CommitListDto>> getCommits(@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "count", defaultValue = "10") int count) {
return new Response<>(commitService.getCommits(offset, count));
}
}

@ -0,0 +1,53 @@
package ru.ulstu.commit.model;
import ru.ulstu.odin.model.annotation.OdinCaption;
import ru.ulstu.odin.model.annotation.OdinDate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
public class CommitListDto {
private final static String DELIMITER = ";";
@OdinCaption("Дата")
@OdinDate(type = OdinDate.OdinDateType.DATETIME)
private final Date date;
@OdinCaption("Пользователь")
private final String userName;
@OdinCaption("Сообщение")
private final String message;
public CommitListDto(String data) {
List<String> datas = Arrays.stream(data.split(DELIMITER))
.filter(d -> d != null && !d.isEmpty())
.collect(Collectors.toList());
if (datas.size() > 2) {
this.userName = datas.get(0);
this.message = datas.get(2);
SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd hh:mm:ss yyyy Z", Locale.US);
try {
this.date = format.parse(datas.get(1));
} catch (ParseException e) {
throw new RuntimeException("wrong commits date");
}
return;
}
throw new RuntimeException("wrong commits data");
}
public String getMessage() {
return message;
}
public Date getDate() {
return date;
}
public String getUserName() {
return userName;
}
}

@ -0,0 +1,62 @@
package ru.ulstu.commit.service;
import org.springframework.stereotype.Service;
import ru.ulstu.commit.model.CommitListDto;
import ru.ulstu.core.model.response.PageableItems;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
public class CommitService {
private static final String COMMITS_FILE_NAME = "/commits.log";
private final List<CommitListDto> commits;
public CommitService() {
commits = getCommits();
}
public PageableItems<CommitListDto> getCommits(int offset, int count) {
return new PageableItems<>(commits.size(), commits.stream()
.skip(offset)
.limit(count)
.collect(Collectors.toList()));
}
private List<CommitListDto> getCommits() {
return getFileContent()
.stream()
.map(CommitListDto::new)
.filter(Objects::nonNull)
.sorted((c1, c2) -> c2.getDate().compareTo(c1.getDate()))
.collect(Collectors.toList());
}
private List<String> getFileContent() {
List<String> result = new ArrayList<>();
//Get file from resources folder
InputStream is = getClass().getResourceAsStream(COMMITS_FILE_NAME);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
try {
while ((line = reader.readLine()) != null) {
result.add(line);
}
} catch (IOException e) {
e.printStackTrace();
}
Collections.reverse(result);
return result;
}
}

@ -0,0 +1,41 @@
package ru.ulstu.configuration;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
@Component
@ConfigurationProperties(prefix = "ng-tracker")
@Validated
public class ApplicationProperties {
@NotBlank
private String baseUrl;
@NotBlank
private String undeadUserLogin;
private boolean devMode;
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getUndeadUserLogin() {
return undeadUserLogin;
}
public void setUndeadUserLogin(String undeadUserLogin) {
this.undeadUserLogin = undeadUserLogin;
}
public boolean isDevMode() {
return devMode;
}
public void setDevMode(boolean devMode) {
this.devMode = devMode;
}
}

@ -0,0 +1,39 @@
package ru.ulstu.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("balance-executor-");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}

@ -0,0 +1,20 @@
package ru.ulstu.configuration;
public class Constants {
public static final String API_1_0 = "/api/1.0/";
public static final String MAIL_ACTIVATE = "Account activation";
public static final String MAIL_RESET = "Password reset";
public static final int MIN_PASSWORD_LENGTH = 6;
public static final String LOGIN_REGEX = "^[_'.@A-Za-z0-9-]*$";
public static final String COOKIES_NAME = "JSESSIONID";
public static final String LOGOUT_URL = "/login?logout";
public static final String SESSION_ID_ATTR = "sessionId";
public static final int SESSION_TIMEOUT_SECONDS = 30 * 60;
public static final String PASSWORD_RESET_REQUEST_PAGE = "/resetRequest";
public static final String PASSWORD_RESET_PAGE = "/reset";
}

@ -0,0 +1,24 @@
package ru.ulstu.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
class ControllersConfiguration {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

@ -0,0 +1,30 @@
package ru.ulstu.configuration;
import org.eclipse.jetty.server.ServerConnector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HttpListenerConfiguration implements EmbeddedServletContainerCustomizer {
@Value("${server.http.port}")
private int httpPort;
private void configureJetty(JettyEmbeddedServletContainerFactory jettyFactory) {
jettyFactory.addServerCustomizers((JettyServerCustomizer) server -> {
ServerConnector serverConnector = new ServerConnector(server);
serverConnector.setPort(httpPort);
server.addConnector(serverConnector);
});
}
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof JettyEmbeddedServletContainerFactory) {
configureJetty((JettyEmbeddedServletContainerFactory) container);
}
}
}

@ -0,0 +1,20 @@
package ru.ulstu.configuration;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfiguration {
@Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
@Bean
public AfterburnerModule afterburnerModule() {
return new AfterburnerModule();
}
}

@ -0,0 +1,21 @@
package ru.ulstu.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import java.nio.charset.StandardCharsets;
@Configuration
public class MailTemplateConfiguration {
@Bean
public ClassLoaderTemplateResolver emailTemplateResolver() {
ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver();
emailTemplateResolver.setPrefix("mail_templates/");
emailTemplateResolver.setSuffix(".html");
emailTemplateResolver.setTemplateMode("HTML5");
emailTemplateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
emailTemplateResolver.setOrder(1);
return emailTemplateResolver;
}
}

@ -0,0 +1,24 @@
package ru.ulstu.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/{articlename:\\w+}");
//registry.addViewController("/admin/{articlename:\\w+}");
registry.addRedirectViewController("/", "/index");
registry.addRedirectViewController("/default", "/index");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/webjars/**")
.addResourceLocations("/webjars/");
}
}

@ -0,0 +1,13 @@
package ru.ulstu.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class PasswordEncoderConfiguration {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}

@ -0,0 +1,118 @@
package ru.ulstu.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import ru.ulstu.user.controller.UserController;
import ru.ulstu.user.model.UserRoleConstants;
import ru.ulstu.user.service.UserService;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
@Value("${server.http.port}")
private int httpPort;
@Value("${server.port}")
private int httpsPort;
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final AuthenticationSuccessHandler authenticationSuccessHandler;
private final LogoutSuccessHandler logoutSuccessHandler;
private final ApplicationProperties applicationProperties;
public SecurityConfiguration(UserService userService,
BCryptPasswordEncoder bCryptPasswordEncoder,
AuthenticationSuccessHandler authenticationSuccessHandler,
LogoutSuccessHandler logoutSuccessHandler,
ApplicationProperties applicationProperties) {
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
this.authenticationSuccessHandler = authenticationSuccessHandler;
this.logoutSuccessHandler = logoutSuccessHandler;
this.applicationProperties = applicationProperties;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable();
if (applicationProperties.isDevMode()) {
log.debug("Security disabled");
http.authorizeRequests()
.anyRequest()
.permitAll();
http.anonymous()
.principal("developer")
.authorities(UserRoleConstants.ADMIN);
} else {
log.debug("Security enabled");
http.authorizeRequests()
.antMatchers(UserController.ACTIVATE_URL).permitAll()
.antMatchers(Constants.PASSWORD_RESET_REQUEST_PAGE).permitAll()
.antMatchers(Constants.PASSWORD_RESET_PAGE).permitAll()
.antMatchers(UserController.URL + UserController.REGISTER_URL).permitAll()
.antMatchers(UserController.URL + UserController.ACTIVATE_URL).permitAll()
.antMatchers(UserController.URL + UserController.PASSWORD_RESET_REQUEST_URL).permitAll()
.antMatchers(UserController.URL + UserController.PASSWORD_RESET_URL).permitAll()
.antMatchers("/swagger-ui.html").hasAuthority(UserRoleConstants.ADMIN)
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.successHandler(authenticationSuccessHandler)
.permitAll()
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.logoutSuccessUrl(Constants.LOGOUT_URL)
.invalidateHttpSession(false)
.clearAuthentication(true)
.deleteCookies(Constants.COOKIES_NAME)
.permitAll();
}
http.portMapper()
.http(httpPort)
.mapsTo(httpsPort)
.and()
.requiresChannel()
.anyRequest()
.requiresSecure();
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/css/**")
.antMatchers("/js/**")
.antMatchers("/templates/**")
.antMatchers("/webjars/**");
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
if (applicationProperties.isDevMode()) {
return;
}
try {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
} catch (Exception e) {
throw new BeanInitializationException("Security configuration failed", e);
}
}
}

@ -0,0 +1,23 @@
package ru.ulstu.configuration;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket swaggerApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(Predicates.not(PathSelectors.regex("/error")))
.build();
}
}

@ -0,0 +1,99 @@
package ru.ulstu.core.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import ru.ulstu.core.error.EntityIdIsNullException;
import ru.ulstu.core.model.ErrorConstants;
import ru.ulstu.core.model.response.Response;
import ru.ulstu.core.model.response.ResponseExtended;
import ru.ulstu.user.error.*;
import ru.ulstu.user.error.*;
import java.util.Set;
import java.util.stream.Collectors;
@RestController
@ControllerAdvice
public class AdviceController {
private final Logger log = LoggerFactory.getLogger(AdviceController.class);
private Response<Void> handleException(ErrorConstants error) {
log.warn(error.toString());
return new Response<>(error);
}
private <E> ResponseExtended<E> handleException(ErrorConstants error, E errorData) {
log.warn(error.toString());
return new ResponseExtended<>(error, errorData);
}
@ExceptionHandler(EntityIdIsNullException.class)
public Response<Void> handleEntityIdIsNullException(Throwable e) {
return handleException(ErrorConstants.ID_IS_NULL);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseExtended<Set<String>> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
final Set<String> errors = e.getBindingResult().getAllErrors().stream()
.filter(error -> error instanceof FieldError)
.map(error -> ((FieldError) error).getField())
.collect(Collectors.toSet());
return handleException(ErrorConstants.VALIDATION_ERROR, errors);
}
@ExceptionHandler(UserIdExistsException.class)
public Response<Void> handleUserIdExistsException(Throwable e) {
return handleException(ErrorConstants.USER_ID_EXISTS);
}
@ExceptionHandler(UserActivationError.class)
public ResponseExtended<String> handleUserActivationError(Throwable e) {
return handleException(ErrorConstants.USER_ACTIVATION_ERROR, e.getMessage());
}
@ExceptionHandler(UserLoginExistsException.class)
public ResponseExtended<String> handleUserLoginExistsException(Throwable e) {
return handleException(ErrorConstants.USER_LOGIN_EXISTS, e.getMessage());
}
@ExceptionHandler(UserEmailExistsException.class)
public ResponseExtended<String> handleUserEmailExistsException(Throwable e) {
return handleException(ErrorConstants.USER_EMAIL_EXISTS, e.getMessage());
}
@ExceptionHandler(UserPasswordsNotValidOrNotMatchException.class)
public Response<Void> handleUserPasswordsNotValidOrNotMatchException(Throwable e) {
return handleException(ErrorConstants.USER_PASSWORDS_NOT_VALID_OR_NOT_MATCH);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseExtended<String> handleUserNotFoundException(Throwable e) {
return handleException(ErrorConstants.USER_NOT_FOUND, e.getMessage());
}
@ExceptionHandler(UserNotActivatedException.class)
public Response<Void> handleUserNotActivatedException(Throwable e) {
return handleException(ErrorConstants.USER_NOT_ACTIVATED);
}
@ExceptionHandler(UserResetKeyError.class)
public ResponseExtended<String> handleUserResetKeyError(Throwable e) {
return handleException(ErrorConstants.USER_RESET_ERROR, e.getMessage());
}
@ExceptionHandler(UserIsUndeadException.class)
public ResponseExtended<String> handleUserIsUndeadException(Throwable e) {
return handleException(ErrorConstants.USER_UNDEAD_ERROR, e.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseExtended<String> handleUnknownException(Throwable e) {
e.printStackTrace();
return handleException(ErrorConstants.UNKNOWN, e.getMessage());
}
}

@ -0,0 +1,6 @@
package ru.ulstu.core.error;
public class EntityIdIsNullException extends RuntimeException {
public EntityIdIsNullException() {
}
}

@ -0,0 +1,7 @@
package ru.ulstu.core.error;
public class OdinException extends RuntimeException {
public OdinException(String message) {
super(message);
}
}

@ -0,0 +1,7 @@
package ru.ulstu.core.error;
public class XlsLoadException extends Exception {
public XlsLoadException(String s) {
super(s);
}
}

@ -0,0 +1,7 @@
package ru.ulstu.core.error;
public class XlsParseException extends Exception {
public XlsParseException(String s) {
super(s);
}
}

@ -0,0 +1,97 @@
package ru.ulstu.core.jpa;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.io.Serializable;
public class OffsetablePageRequest implements Pageable, Serializable {
private final int offset;
private final int count;
private final Sort sort;
public OffsetablePageRequest(int offset, int count) {
this(offset, count, null);
}
public OffsetablePageRequest(int offset, int count, Sort.Direction direction, String... properties) {
this(offset, count, new Sort(direction, properties));
}
public OffsetablePageRequest(int offset, int count, Sort sort) {
if (offset < 0) {
throw new IllegalArgumentException("Offset value must not be less than zero!");
}
if (count < 1) {
throw new IllegalArgumentException("Count value must not be less than one!");
}
this.offset = offset;
this.count = count;
this.sort = sort;
}
@Override
public Sort getSort() {
return sort;
}
@Override
public int getPageSize() {
return count;
}
@Override
public int getPageNumber() {
return offset / count;
}
@Override
public int getOffset() {
return offset;
}
@Override
public boolean hasPrevious() {
return offset > 0;
}
@Override
public Pageable next() {
return new OffsetablePageRequest(getOffset() + getPageSize(), getPageSize(), getSort());
}
@Override
public Pageable previousOrFirst() {
return hasPrevious() ? previous() : first();
}
public Pageable previous() {
return getOffset() == 0 ? this : new OffsetablePageRequest(getOffset() - getPageSize(), getPageSize(), getSort());
}
@Override
public Pageable first() {
return new OffsetablePageRequest(0, getPageSize(), getSort());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final OffsetablePageRequest other = (OffsetablePageRequest) obj;
return this.offset == other.offset && this.count == other.count;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + offset;
result = prime * result + count;
return result;
}
}

@ -0,0 +1,82 @@
package ru.ulstu.core.model;
import javax.persistence.*;
import java.io.Serializable;
@MappedSuperclass
public abstract class BaseEntity implements Serializable, Comparable {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private Integer id;
@Version
private Integer version;
public BaseEntity() {
}
public BaseEntity(Integer id, Integer version) {
this.id = id;
this.version = version;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!getClass().isAssignableFrom(obj.getClass())) {
return false;
}
BaseEntity other = (BaseEntity) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (id == null ? 0 : id.hashCode());
return result;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"id=" + id +
", version=" + version +
'}';
}
@Override
public int compareTo(Object o) {
return id != null ? id.compareTo(((BaseEntity) o).getId()) : -1;
}
public void reset() {
this.id = null;
this.version = null;
}
}

@ -0,0 +1,38 @@
package ru.ulstu.core.model;
public enum ErrorConstants {
UNKNOWN(0, "Unknown error"),
ID_IS_NULL(1, "Id of entity has null value"),
VALIDATION_ERROR(2, "Validation error"),
USER_ID_EXISTS(100, "New user can't have id"),
USER_ACTIVATION_ERROR(101, "Invalid activation key"),
USER_EMAIL_EXISTS(102, "User with same email already exists"),
USER_LOGIN_EXISTS(103, "User with same login already exists"),
USER_PASSWORDS_NOT_VALID_OR_NOT_MATCH(104, "User passwords is not valid or not match"),
USER_NOT_FOUND(105, "User is not found"),
USER_NOT_ACTIVATED(106, "User is not activated"),
USER_RESET_ERROR(107, "Invalid reset key"),
USER_UNDEAD_ERROR(108, "Can't edit/delete that user"),
FILE_UPLOAD_ERROR(110, "File upload error");
private int code;
private String message;
ErrorConstants(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return String.format("%d: %s", code, message);
}
}

@ -0,0 +1,34 @@
package ru.ulstu.core.model;
import java.util.ArrayList;
import java.util.List;
public class TreeDto {
private Integer id;
private String text;
private List<TreeDto> children = new ArrayList<>();
public TreeDto() {
}
public <T extends TreeEntity> TreeDto(TreeEntity item) {
this.text = item.toString();
this.id = item.getId();
}
public TreeDto(String rootName) {
this.text = rootName;
}
public String getText() {
return text;
}
public List<TreeDto> getChildren() {
return children;
}
public Integer getId() {
return id;
}
}

@ -0,0 +1,16 @@
package ru.ulstu.core.model;
import java.util.List;
public interface TreeEntity<T> {
Integer getId();
List<T> getChildren();
void setChildren(List<T> children);
T getParent();
void setParent(T parent);
}

@ -0,0 +1,24 @@
package ru.ulstu.core.model.response;
class ControllerResponse<D, E> {
private D data;
private ControllerResponseError<E> error;
ControllerResponse(D data) {
this.data = data;
this.error = null;
}
ControllerResponse(ControllerResponseError<E> error) {
this.data = null;
this.error = error;
}
public D getData() {
return data;
}
public ControllerResponseError<E> getError() {
return error;
}
}

@ -0,0 +1,25 @@
package ru.ulstu.core.model.response;
import ru.ulstu.core.model.ErrorConstants;
class ControllerResponseError<D> {
private ErrorConstants description;
private D data;
ControllerResponseError(ErrorConstants description, D data) {
this.description = description;
this.data = data;
}
public int getCode() {
return description.getCode();
}
public String getMessage() {
return description.getMessage();
}
public D getData() {
return data;
}
}

@ -0,0 +1,21 @@
package ru.ulstu.core.model.response;
import java.util.Collection;
public class PageableItems<T> {
private final long count;
private final Collection<T> items;
public PageableItems(long count, Collection<T> items) {
this.count = count;
this.items = items;
}
public long getCount() {
return count;
}
public Collection<T> getItems() {
return items;
}
}

@ -0,0 +1,16 @@
package ru.ulstu.core.model.response;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import ru.ulstu.core.model.ErrorConstants;
public class Response<D> extends ResponseEntity<Object> {
public Response(D data) {
super(new ControllerResponse<D, Void>(data), HttpStatus.OK);
}
public Response(ErrorConstants error) {
super(new ControllerResponse<Void, Void>(new ControllerResponseError<>(error, null)), HttpStatus.OK);
}
}

@ -0,0 +1,12 @@
package ru.ulstu.core.model.response;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import ru.ulstu.core.model.ErrorConstants;
public class ResponseExtended<E> extends ResponseEntity<Object> {
public ResponseExtended(ErrorConstants error, E errorData) {
super(new ControllerResponse<Void, E>(new ControllerResponseError<E>(error, errorData)), HttpStatus.OK);
}
}

@ -0,0 +1,11 @@
package ru.ulstu.core.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import java.io.Serializable;
@NoRepositoryBean
public interface JpaDetachableRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
void detach(T t);
}

@ -0,0 +1,22 @@
package ru.ulstu.core.repository;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import javax.persistence.EntityManager;
import java.io.Serializable;
public class JpaDetachableRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
implements JpaDetachableRepository<T, ID> {
private EntityManager entityManager;
public JpaDetachableRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
@Override
public void detach(T t) {
entityManager.detach(t);
}
}

@ -0,0 +1,31 @@
package ru.ulstu.core.service;
import org.springframework.stereotype.Service;
import ru.ulstu.core.model.TreeDto;
import ru.ulstu.core.model.TreeEntity;
import java.util.List;
import java.util.function.Predicate;
@Service
public class TreeService<T extends TreeEntity> {
public TreeDto getTree(String rootName, List<T> rootItems) {
return addChildNode(new TreeDto(rootName), rootItems, element -> true);
}
public TreeDto getTree(String rootName, List<T> rootItems, Predicate<T> filterPredicate) {
return addChildNode(new TreeDto(rootName), rootItems, filterPredicate);
}
private TreeDto addChildNode(TreeDto currentRoot, List<T> children, Predicate<T> filterPredicate) {
if (children != null) {
children.stream()
.filter(filterPredicate)
.forEach(item -> {
TreeDto newNode = new TreeDto(item);
currentRoot.getChildren().add(addChildNode(newNode, item.getChildren(), filterPredicate));
});
}
return currentRoot;
}
}

@ -0,0 +1,207 @@
package ru.ulstu.core.service;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import ru.ulstu.core.error.XlsParseException;
import java.io.*;
public class XlsDocumentBuilder {
private static final int DEFAULT_SHEET_NUM = 0;
private File documentFile;
private Workbook workBook;
private Sheet currentSheet;
/**
* Constructor for reading and writing data from/to *.[xls|xlsx] document
*
* @param file contains existing document for reading or new document to save
*/
public XlsDocumentBuilder(File file) throws IOException, XlsParseException {
this.documentFile = file;
if (file.exists()) {
workBook = getWorkBook(file);
currentSheet = workBook.getSheetAt(DEFAULT_SHEET_NUM);
} else {
workBook = new XSSFWorkbook();
currentSheet = workBook.createSheet();
}
}
private Workbook getWorkBook(File file) throws XlsParseException, IOException {
InputStream inputStream = new PushbackInputStream(new FileInputStream(file), 4096);
if (isXlsx(inputStream)) {
return new XSSFWorkbook(inputStream);
} else if (isExcel(inputStream)) {
return new HSSFWorkbook(inputStream);
}
throw new XlsParseException("Wrong document format");
}
/**
* Change active sheet to write or read data
*
* @param index index of sheet to activate
*/
public XlsDocumentBuilder setActiveSheet(int index) {
workBook.setActiveSheet(index);
currentSheet = workBook.getSheetAt(index);
return this;
}
/**
* Create new sheet in document and set it active
*
* @param sheetName
*/
public XlsDocumentBuilder insertNewSheet(String sheetName) {
currentSheet = workBook.createSheet(sheetName);
workBook.setActiveSheet(getSheetCount() - 1);
return this;
}
public XlsDocumentBuilder insertNewSheet(String sheetName, int order) {
insertNewSheet(sheetName);
workBook.setSheetOrder(sheetName, order);
return this;
}
/**
* Returns number of sheet in document
*
* @return sheets count
*/
public int getSheetCount() {
return workBook.getNumberOfSheets();
}
/**
* Returns number of rows in sheet
*
* @return rows count
*/
public int getRowCount() {
return currentSheet.getLastRowNum();
}
/**
* Returns number of columns in sheet
*
* @return columns count
*/
public int getColumnCount() {
Row row = currentSheet.getRow(getRowCount());
if (row == null) {
return 0;
}
return row.getLastCellNum() - 1;
}
/**
* Returns converted to string representation of cell value
*
* @param rowIndex row index of current sheet
* @param colIndex column index of current sheet
* @return string value of cell
*/
public String getCellAsString(int rowIndex, int colIndex) {
Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex);
cell.setCellType(Cell.CELL_TYPE_STRING);
return cell.getStringCellValue();
}
/**
* Sets string cell value
*
* @param rowIndex row index of current sheet
* @param colIndex column index of current sheet
*/
public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, String value) {
if (currentSheet.getRow(rowIndex) == null) {
currentSheet.createRow(rowIndex);
}
if (currentSheet.getRow(rowIndex).getCell(colIndex) == null) {
currentSheet.getRow(rowIndex).createCell(colIndex);
}
Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex);
cell.setCellValue(value);
setColumnAutosize(colIndex, colIndex);
return this;
}
public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, int value) {
return setCellValue(rowIndex, colIndex, String.valueOf(value));
}
public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, long value) {
return setCellValue(rowIndex, colIndex, String.valueOf(value));
}
/**
* Save current file
*/
public XlsDocumentBuilder save() throws IOException {
OutputStream out = new FileOutputStream(documentFile);
workBook.write(out);
return this;
}
private boolean isExcel(InputStream i) throws IOException {
return (POIFSFileSystem.hasPOIFSHeader(i) || POIXMLDocument.hasOOXMLHeader(i));
}
private boolean isXlsx(InputStream i) throws IOException {
return POIXMLDocument.hasOOXMLHeader(i);
}
public int getActiveSheetIndex() {
return workBook.getActiveSheetIndex();
}
public XlsDocumentBuilder mergeCells(int rowFrom, int rowTo, int colFrom, int colTo) {
currentSheet.addMergedRegion(new CellRangeAddress(rowFrom, rowTo, colFrom, colTo));
return this;
}
public void setRegionBorderWithMedium(int rowFrom, int rowTo, int colFrom, int colTo) {
for (int row = rowFrom; row < rowTo; row++) {
for (int col = colFrom; col <= colTo; col++) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(row, row, col, col);
RegionUtil.setBorderBottom(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
RegionUtil.setBorderLeft(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
RegionUtil.setBorderRight(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
RegionUtil.setBorderTop(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
}
}
}
public XlsDocumentBuilder setColumnAutosize(int from, int to) {
for (int col = from; col <= to; col++) {
currentSheet.autoSizeColumn(col, true);
}
return this;
}
public XlsDocumentBuilder setRowAutosize(int from, int to) {
CellStyle style = workBook.createCellStyle();
style.setWrapText(true);
for (int row = from; row <= to; row++) {
for (int col = 0; col <= currentSheet.getRow(row).getLastCellNum(); col++) {
if (currentSheet.getRow(row).getCell(col) != null) {
currentSheet.getRow(row).getCell(col).setCellStyle(style);
}
}
}
return this;
}
public XlsDocumentBuilder deleteSheet(int index) {
workBook.removeSheetAt(index);
return this;
}
}

@ -0,0 +1,46 @@
package ru.ulstu.core.util;
import java.time.*;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
public class DateUtils {
public static Date clearTime(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.HOUR, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
public static Calendar getCalendar(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
}
public static List<Month> getMonths () {
return Arrays.asList(Month.values());
}
public static Date instantToDate(Instant instant) {
return Date.from(instant);
}
public static Date localDateToDate(LocalDate localDate) {
return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
}
public static Date localDateTimeToDate(LocalDateTime localDate) {
return Date.from(localDate.atZone(ZoneId.systemDefault()).toInstant());
}
public static Date localTimeToDate(LocalTime localTime) {
return Date.from(localTime.atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant());
}
}

@ -0,0 +1,17 @@
package ru.ulstu.core.util;
public class NumberUtils {
public static Double ceil(Double number) {
if (number == null) {
return 0.0;
}
return Double.valueOf(Math.ceil(number));
}
public static Double round(Double number) {
if (number == null) {
return 0.0;
}
return Double.valueOf(Math.ceil(number * 100)) / 100;
}
}

@ -0,0 +1,12 @@
package ru.ulstu.core.util;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class StreamApiUtils {
public static <T, R> List<T> convert(List<R> entitites, Function<R, T> converter) {
return entitites.stream().map(e -> converter.apply(e)).collect(Collectors.toList());
}
}

@ -0,0 +1,37 @@
package ru.ulstu.odin.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import ru.ulstu.core.model.response.Response;
import ru.ulstu.odin.model.OdinDto;
import ru.ulstu.odin.model.OdinMetadata;
import ru.ulstu.odin.service.OdinService;
public abstract class OdinController<L, E extends OdinDto> {
public static final String META_LIST_URL = "/meta/list";
public static final String META_ELEMENT_URL = "/meta/element";
private Class<L> listDtoClass;
private Class<E> elementDtoClass;
@Autowired
private OdinService<L, E> odinService;
public OdinController(Class<L> listDtoClass) {
this(listDtoClass, null);
}
public OdinController(Class<L> listDtoClass, Class<E> elementDtoClass) {
this.listDtoClass = listDtoClass;
this.elementDtoClass = elementDtoClass;
}
@GetMapping(META_LIST_URL)
public Response<OdinMetadata> getListModel() {
return new Response<>(odinService.getListModel(listDtoClass));
}
@GetMapping(META_ELEMENT_URL)
public Response<OdinMetadata> getElementModel() {
return new Response<>(odinService.getElementModel(elementDtoClass));
}
}

@ -0,0 +1,9 @@
package ru.ulstu.odin.model;
import java.lang.reflect.Field;
public class OdinBooleanField extends OdinField {
public OdinBooleanField(Field field) {
super(field, OdinFieldType.BOOLEAN);
}
}

@ -0,0 +1,28 @@
package ru.ulstu.odin.model;
import ru.ulstu.core.error.OdinException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class OdinCollectionField extends OdinField {
private final String path;
public OdinCollectionField(Field field) {
super(field, OdinFieldType.COLLECTION);
ParameterizedType genericType = (ParameterizedType) field.getGenericType();
Type fieldElementClass = genericType.getActualTypeArguments()[0];
try {
OdinDto someInstance = (OdinDto) ((Class) (fieldElementClass)).newInstance();
this.path = someInstance.getControllerPath();
} catch (IllegalAccessException | InstantiationException e) {
throw new OdinException(String.format("Can't create new instance, check default constructor of %s",
fieldElementClass.getTypeName()));
}
}
public String getPath() {
return path;
}
}

@ -0,0 +1,18 @@
package ru.ulstu.odin.model;
import ru.ulstu.odin.model.annotation.OdinDate;
import java.lang.reflect.Field;
public class OdinDateField extends OdinField {
private final OdinDate.OdinDateType type;
public OdinDateField(Field field) {
super(field, OdinFieldType.DATE);
this.type = getValue(OdinDate.class, "type", OdinDate.OdinDateType.class);
}
public String getType() {
return type.toString();
}
}

@ -0,0 +1,14 @@
package ru.ulstu.odin.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
public interface OdinDto {
Object getId();
@JsonProperty("view")
String getViewValue();
@JsonIgnore
String getControllerPath();
}

@ -0,0 +1,154 @@
package ru.ulstu.odin.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import ru.ulstu.core.error.OdinException;
import ru.ulstu.odin.model.annotation.OdinCaption;
import ru.ulstu.odin.model.annotation.OdinReadOnly;
import ru.ulstu.odin.model.annotation.OdinVisible;
import javax.validation.constraints.NotNull;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
public abstract class OdinField implements Comparable {
public enum OdinFieldType {
BOOLEAN,
DATE,
NUMERIC,
STRING,
COLLECTION,
OBJECT,
UNKNOWN;
@Override
public String toString() {
return this.name().toLowerCase();
}
}
private Field field;
protected final OdinFieldType fieldType;
protected final String fieldName;
protected final String caption;
protected final OdinVisible.OdinVisibleType visible;
protected final boolean readOnly;
protected final boolean notEmpty;
public OdinField(Field field, OdinFieldType fieldType) {
this.field = field;
this.fieldName = getFieldName(field);
this.caption = Optional.ofNullable(getValueFromAnnotation(OdinCaption.class, "value"))
.map(value -> cast(value, String.class))
.orElse(fieldName);
this.visible = getValue(OdinVisible.class, "type", OdinVisible.OdinVisibleType.class);
this.readOnly = field.isAnnotationPresent(OdinReadOnly.class);
this.notEmpty = field.isAnnotationPresent(NotBlank.class)
|| field.isAnnotationPresent(NotEmpty.class)
|| field.isAnnotationPresent(NotNull.class);
this.fieldType = fieldType;
}
private String getFieldName(Field field) {
if (field.isAnnotationPresent(JsonProperty.class)) {
final String fieldName = cast(getValueFromAnnotation(JsonProperty.class, "value"), String.class);
return Optional.ofNullable(fieldName).filter(value -> !value.isEmpty()).orElse(field.getName());
}
return field.getName();
}
private Object getDefaultValueFromAnnotation(Class<? extends Annotation> annotationClass, String valueName) {
try {
final Method method = annotationClass.getDeclaredMethod(valueName);
return method.getDefaultValue();
} catch (NoSuchMethodException e) {
throw new OdinException(String.format("Default value %s is not found in annotation %s",
valueName, annotationClass.getSimpleName()));
}
}
private Object getValueFromAnnotation(Class<? extends Annotation> annotationClass, String valueName) {
if (!field.isAnnotationPresent(annotationClass)) {
return null;
}
try {
final Method method = annotationClass.getDeclaredMethod(valueName);
return method.invoke(field.getAnnotation(annotationClass));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new OdinException(String.format("Value %s is not found in annotation %s",
valueName, annotationClass.getSimpleName()));
}
}
private <T> T cast(Object value, Class<T> clazz) {
try {
return clazz.cast(value);
} catch (ClassCastException e) {
return null;
}
}
protected <T> T getValue(Class<? extends Annotation> annotationClass, String valueName, Class<T> clazz) {
if (field.isAnnotationPresent(annotationClass)) {
return cast(getValueFromAnnotation(annotationClass, valueName), clazz);
}
return cast(getDefaultValueFromAnnotation(annotationClass, valueName), clazz);
}
public String getFieldType() {
return fieldType.toString();
}
public String getFieldName() {
return fieldName;
}
public String getCaption() {
return caption;
}
public String getVisible() {
return visible.toString();
}
public boolean isReadOnly() {
return readOnly;
}
public boolean isNotEmpty() {
return notEmpty;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OdinField odinField = (OdinField) o;
return Objects.equals(fieldName, odinField.fieldName);
}
@Override
public int hashCode() {
return Objects.hash(fieldName);
}
@Override
public String toString() {
return getClass().getSimpleName() + " {" +
"fieldName='" + fieldName + '\'' +
", fieldType='" + fieldType + '\'' +
'}';
}
@Override
public int compareTo(Object o) {
final String thisName = Optional.ofNullable(fieldName).orElse("");
final String oName = Optional.ofNullable(o).map(obj -> ((OdinField) obj).fieldName).orElse("");
return thisName.compareTo(oName);
}
}

@ -0,0 +1,26 @@
package ru.ulstu.odin.model;
import ru.ulstu.core.error.OdinException;
import java.util.List;
public class OdinMetadata {
private final boolean odinDto;
private final List<OdinField> fields;
public OdinMetadata(boolean odinDto, List<OdinField> fields) {
if (fields == null) {
throw new OdinException("Fields list can't be null");
}
this.odinDto = odinDto;
this.fields = fields;
}
public boolean isOdinDto() {
return odinDto;
}
public List<OdinField> getFields() {
return fields;
}
}

@ -0,0 +1,30 @@
package ru.ulstu.odin.model;
import ru.ulstu.odin.model.annotation.OdinNumeric;
import java.lang.reflect.Field;
public class OdinNumericField extends OdinField {
private final boolean positiveOnly;
private final int precision;
private final int scale;
public OdinNumericField(Field field) {
super(field, OdinFieldType.NUMERIC);
this.positiveOnly = getValue(OdinNumeric.class, "positiveOnly", Boolean.class);
this.precision = getValue(OdinNumeric.class, "precision", Integer.class);
this.scale = getValue(OdinNumeric.class, "scale", Integer.class);
}
public boolean isPositiveOnly() {
return positiveOnly;
}
public int getPrecision() {
return precision;
}
public int getScale() {
return scale;
}
}

@ -0,0 +1,26 @@
package ru.ulstu.odin.model;
import ru.ulstu.core.error.OdinException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
public class OdinObjectField extends OdinField {
private final String path;
public OdinObjectField(Field field) {
super(field, OdinFieldType.OBJECT);
Type fieldElementClass = field.getType();
try {
OdinDto someInstance = (OdinDto) ((Class) (fieldElementClass)).newInstance();
this.path = someInstance.getControllerPath();
} catch (IllegalAccessException | InstantiationException e) {
throw new OdinException(String.format("Can't create new instance, check default constructor of %s",
fieldElementClass.getTypeName()));
}
}
public String getPath() {
return path;
}
}

@ -0,0 +1,37 @@
package ru.ulstu.odin.model;
import org.hibernate.validator.constraints.Email;
import ru.ulstu.odin.model.annotation.OdinString;
import ru.ulstu.odin.model.annotation.OdinString.OdinStringType;
import javax.validation.constraints.Size;
import java.lang.reflect.Field;
import static ru.ulstu.odin.model.annotation.OdinString.OdinStringType.EMAIL;
public class OdinStringField extends OdinField {
private final int minLength;
private final int maxLength;
private final OdinStringType type;
public OdinStringField(Field field) {
super(field, OdinFieldType.STRING);
this.minLength = getValue(Size.class, "min", Integer.class);
this.maxLength = getValue(Size.class, "max", Integer.class);
this.type = field.isAnnotationPresent(Email.class)
? EMAIL
: getValue(OdinString.class, "type", OdinStringType.class);
}
public int getMinLength() {
return minLength;
}
public int getMaxLength() {
return maxLength;
}
public String getType() {
return type.toString();
}
}

@ -0,0 +1,18 @@
package ru.ulstu.odin.model;
public class OdinVoid implements OdinDto {
@Override
public Object getId() {
return null;
}
@Override
public String getViewValue() {
return null;
}
@Override
public String getControllerPath() {
return null;
}
}

@ -0,0 +1,14 @@
package ru.ulstu.odin.model.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, ANNOTATION_TYPE})
public @interface OdinCaption {
String value();
}

@ -0,0 +1,23 @@
package ru.ulstu.odin.model.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, ANNOTATION_TYPE})
public @interface OdinDate {
enum OdinDateType {
DATETIME, DATE, TIME;
@Override
public String toString() {
return this.name().toLowerCase();
}
}
OdinDateType type() default OdinDateType.DATE;
}

@ -0,0 +1,18 @@
package ru.ulstu.odin.model.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, ANNOTATION_TYPE})
public @interface OdinNumeric {
boolean positiveOnly() default false;
int precision() default 10;
int scale() default 0;
}

@ -0,0 +1,13 @@
package ru.ulstu.odin.model.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, ANNOTATION_TYPE})
public @interface OdinReadOnly {
}

@ -0,0 +1,23 @@
package ru.ulstu.odin.model.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, ANNOTATION_TYPE})
public @interface OdinString {
enum OdinStringType {
STRING, PASSWORD, TEXT, EMAIL, HREF;
@Override
public String toString() {
return this.name().toLowerCase();
}
}
OdinStringType type() default OdinStringType.STRING;
}

@ -0,0 +1,23 @@
package ru.ulstu.odin.model.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD, ANNOTATION_TYPE})
public @interface OdinVisible {
enum OdinVisibleType {
ALL, ON_CREATE, ON_UPDATE, NONE;
@Override
public String toString() {
return this.name().toLowerCase();
}
}
OdinVisibleType type() default OdinVisibleType.ALL;
}

@ -0,0 +1,101 @@
package ru.ulstu.odin.service;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import ru.ulstu.core.error.OdinException;
import ru.ulstu.odin.model.*;
import ru.ulstu.odin.model.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class OdinService<L, E extends OdinDto> {
private final Logger log = LoggerFactory.getLogger(OdinService.class);
private final Map<String, OdinField.OdinFieldType> aliases;
public OdinService() {
aliases = new HashMap<>();
aliases.put("boolean", OdinField.OdinFieldType.BOOLEAN);
aliases.put("date", OdinField.OdinFieldType.DATE);
aliases.put("instant", OdinField.OdinFieldType.DATE);
aliases.put("localdate", OdinField.OdinFieldType.DATE);
aliases.put("localtime", OdinField.OdinFieldType.DATE);
aliases.put("localdatetime", OdinField.OdinFieldType.DATE);
aliases.put("int", OdinField.OdinFieldType.NUMERIC);
aliases.put("integer", OdinField.OdinFieldType.NUMERIC);
aliases.put("double", OdinField.OdinFieldType.NUMERIC);
aliases.put("float", OdinField.OdinFieldType.NUMERIC);
aliases.put("long", OdinField.OdinFieldType.NUMERIC);
aliases.put("string", OdinField.OdinFieldType.STRING);
aliases.put("collection", OdinField.OdinFieldType.COLLECTION);
aliases.put("object", OdinField.OdinFieldType.OBJECT);
}
private OdinField.OdinFieldType getFieldTypeByTypeAlias(String fieldType) {
return Optional.ofNullable(aliases.get(fieldType)).orElse(OdinField.OdinFieldType.UNKNOWN);
}
private String getTypeAlias(String fieldType) {
return fieldType.replaceAll(".*\\.", "").toLowerCase().trim();
}
private OdinField.OdinFieldType getFieldType(Field field) {
final String typeAlias;
if (Collection.class.isAssignableFrom(field.getType())) {
typeAlias = "collection";
} else if (OdinDto.class.isAssignableFrom(field.getType())) {
typeAlias = "object";
} else {
typeAlias = getTypeAlias(field.getGenericType().getTypeName());
}
return getFieldTypeByTypeAlias(typeAlias);
}
private <T> List<OdinField> getDtoMetaModel(Class<T> dtoClass) {
return Arrays.stream(dtoClass.getDeclaredFields())
.filter(field -> !field.isAnnotationPresent(JsonIgnore.class)
&& !Modifier.isStatic(field.getModifiers()))
.map(field -> {
OdinField.OdinFieldType fieldType = getFieldType(field);
switch (fieldType) {
case BOOLEAN:
return new OdinBooleanField(field);
case DATE:
return new OdinDateField(field);
case NUMERIC:
return new OdinNumericField(field);
case STRING:
return new OdinStringField(field);
case COLLECTION:
return new OdinCollectionField(field);
case OBJECT:
return new OdinObjectField(field);
default:
log.debug("Unknown type {}. Skip field {} of DTO {}.",
field.getGenericType().getTypeName(), field.getName(), dtoClass.getSimpleName());
}
return null;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public OdinMetadata getListModel(Class<L> listDtoClass) {
if (listDtoClass == null) {
throw new OdinException("List DTO class is null");
}
return new OdinMetadata(OdinDto.class.isAssignableFrom(listDtoClass), getDtoMetaModel(listDtoClass));
}
public OdinMetadata getElementModel(Class<E> elementDtoClass) {
return elementDtoClass == null
? null :
new OdinMetadata(true, getDtoMetaModel(elementDtoClass));
}
}

@ -0,0 +1,4 @@
package ru.ulstu.odinexample.controller;
public class OdinExampleController {
}

@ -0,0 +1,4 @@
package ru.ulstu.odinexample.model;
public class OdinExampleDto {
}

@ -0,0 +1,103 @@
package ru.ulstu.odinexample.model;
import ru.ulstu.core.util.DateUtils;
import ru.ulstu.odin.model.annotation.OdinCaption;
import ru.ulstu.odin.model.annotation.OdinDate;
import ru.ulstu.odin.model.annotation.OdinNumeric;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
public class OdinExampleListDto {
@OdinCaption("instant")
@OdinDate(type = OdinDate.OdinDateType.DATETIME)
private Instant instant;
@OdinCaption("date")
private Date date;
@OdinCaption("localdate")
private LocalDate localDate;
@OdinCaption("localtime")
@OdinDate(type = OdinDate.OdinDateType.TIME)
private LocalTime localTime;
@OdinCaption("localdatetime")
@OdinDate(type = OdinDate.OdinDateType.DATETIME)
private LocalDateTime localDateTime;
@OdinCaption("int")
private int intval;
@OdinCaption("int+settings")
@OdinNumeric(precision = 5, scale = 2)
private int intvalset;
@OdinCaption("float")
private float floatval;
@OdinCaption("double")
private double aDouble;
@OdinCaption("double+set")
@OdinNumeric(precision = 5, scale = 3)
private double aDoubles;
@OdinCaption("int+positive")
@OdinNumeric(positiveOnly = true, scale = 2)
private int invalpos;
public OdinExampleListDto() {
this.instant = Instant.now();
this.date = new Date();
this.localDate = LocalDate.now();
this.localTime = LocalTime.now();
this.localDateTime = LocalDateTime.now();
intval = -134;
intvalset = 1343423232;
floatval = 2323.44F;
aDouble = -232323.43434;
aDoubles = 0.456456456;
invalpos = -23232323;
}
public Date getInstant() {
return DateUtils.instantToDate(instant);
}
public Date getDate() {
return date;
}
public Date getLocalDate() {
return DateUtils.localDateToDate(localDate);
}
public Date getLocalTime() {
return DateUtils.localTimeToDate(localTime);
}
public Date getLocalDateTime() {
return DateUtils.localDateTimeToDate(localDateTime);
}
public int getIntval() {
return intval;
}
public int getIntvalset() {
return intvalset;
}
public float getFloatval() {
return floatval;
}
public double getaDouble() {
return aDouble;
}
public double getaDoubles() {
return aDoubles;
}
public int getInvalpos() {
return invalpos;
}
}

@ -0,0 +1,4 @@
package ru.ulstu.odinexample.service;
public class OdinExampleService {
}

@ -0,0 +1,23 @@
package ru.ulstu.user.component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
public final class IpAddressResolver {
private static final String CLIENT_IP_HEADER = "Client-IP";
private static final String FORWARDED_FOR_HEADER = "X-Forwarded-For";
public static String getRemoteAddr(HttpServletRequest request) {
String headerClientIp = request.getHeader("");
String headerXForwardedFor = request.getHeader(HttpServletRequest.FORM_AUTH);
if (StringUtils.isEmpty(request.getRemoteAddr()) && !StringUtils.isEmpty(headerClientIp)) {
return headerClientIp;
}
if (!StringUtils.isEmpty(headerXForwardedFor)) {
return headerXForwardedFor;
}
return request.getRemoteAddr();
}
}

@ -0,0 +1,45 @@
package ru.ulstu.user.component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import ru.ulstu.configuration.Constants;
import ru.ulstu.user.service.UserSessionService;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@Component
public class UserSessionLoginHandler extends SavedRequestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final Logger log = LoggerFactory.getLogger(UserSessionLoginHandler.class);
private final UserSessionService userSessionService;
public UserSessionLoginHandler(UserSessionService userSessionService) {
super();
this.userSessionService = userSessionService;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
final String login = authentication.getName();
final String ipAddress = IpAddressResolver.getRemoteAddr(request);
final String host = request.getRemoteHost();
log.debug("Authentication Success for {}@{} ({})", login, ipAddress, host);
HttpSession session = request.getSession(false);
if (session != null) {
final String sessionId = session.getId();
userSessionService.createUserSession(sessionId, login, ipAddress, host);
session.setAttribute(Constants.SESSION_ID_ATTR, sessionId);
session.setMaxInactiveInterval(Constants.SESSION_TIMEOUT_SECONDS);
}
}
}

@ -0,0 +1,49 @@
package ru.ulstu.user.component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.stereotype.Component;
import ru.ulstu.configuration.Constants;
import ru.ulstu.user.service.UserSessionService;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@Component
public class UserSessionLogoutHandler extends SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler {
private final Logger log = LoggerFactory.getLogger(UserSessionLogoutHandler.class);
private final UserSessionService userSessionService;
public UserSessionLogoutHandler(UserSessionService userSessionService) {
this.userSessionService = userSessionService;
setDefaultTargetUrl(Constants.LOGOUT_URL);
}
@Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if (authentication == null) {
super.onLogoutSuccess(request, response, authentication);
return;
}
final String login = authentication.getName();
final String ipAddress = IpAddressResolver.getRemoteAddr(request);
final String host = request.getRemoteHost();
log.debug("Logout Success for {}@{} ({})", login, ipAddress, host);
HttpSession session = request.getSession(false);
if (session != null) {
final String sessionId = session.getAttribute(Constants.SESSION_ID_ATTR).toString();
userSessionService.closeUserSession(sessionId);
session.removeAttribute(Constants.SESSION_ID_ATTR);
session.invalidate();
}
super.onLogoutSuccess(request, response, authentication);
}
}

@ -0,0 +1,158 @@
package ru.ulstu.user.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.*;
import ru.ulstu.configuration.Constants;
import ru.ulstu.core.model.response.PageableItems;
import ru.ulstu.core.model.response.Response;
import ru.ulstu.odin.controller.OdinController;
import ru.ulstu.odin.model.OdinMetadata;
import ru.ulstu.odin.model.OdinVoid;
import ru.ulstu.odin.service.OdinService;
import ru.ulstu.user.model.*;
import ru.ulstu.user.service.UserService;
import ru.ulstu.user.service.UserSessionService;
import ru.ulstu.user.model.*;
import javax.validation.Valid;
import static ru.ulstu.user.controller.UserController.URL;
@RestController
@RequestMapping(URL)
public class UserController extends OdinController<UserListDto, UserDto> {
public static final String URL = Constants.API_1_0 + "users";
public static final String ROLES_URL = "/roles";
public static final String ROLES_META_URL = ROLES_URL + OdinController.META_LIST_URL;
public static final String SESSIONS_URL = "/sessions";
public static final String SESSIONS_META_URL = SESSIONS_URL + OdinController.META_LIST_URL;
public static final String REGISTER_URL = "/register";
public static final String ACTIVATE_URL = "/activate";
public static final String PASSWORD_RESET_REQUEST_URL = "/password-reset-request";
public static final String PASSWORD_RESET_URL = "/password-reset";
private final Logger log = LoggerFactory.getLogger(UserController.class);
private final UserService userService;
private final UserSessionService userSessionService;
private final OdinService<UserRoleDto, UserRoleDto> odinRolesService;
private final OdinService<UserSessionListDto, OdinVoid> odinSessionsService;
public UserController(UserService userService,
UserSessionService userSessionService,
OdinService<UserRoleDto, UserRoleDto> odinRolesService,
OdinService<UserSessionListDto, OdinVoid> odinSessionsService) {
super(UserListDto.class, UserDto.class);
this.userService = userService;
this.userSessionService = userSessionService;
this.odinRolesService = odinRolesService;
this.odinSessionsService = odinSessionsService;
}
@GetMapping(ROLES_URL)
@Secured(UserRoleConstants.ADMIN)
public Response<PageableItems<UserRoleDto>> getUserRoles() {
log.debug("REST: UserController.getUserRoles()");
return new Response<>(userService.getUserRoles());
}
@GetMapping(ROLES_META_URL)
@Secured(UserRoleConstants.ADMIN)
public Response<OdinMetadata> getUserRolesMetaData() {
log.debug("REST: UserController.getUserRolesMetaData()");
return new Response<>(odinRolesService.getListModel(UserRoleDto.class));
}
@GetMapping(SESSIONS_URL)
@Secured(UserRoleConstants.ADMIN)
public Response<PageableItems<UserSessionListDto>> getUserSessions(@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "count", defaultValue = "10") int count) {
log.debug("REST: UserController.getUserSessions()");
return new Response<>(userSessionService.getSessions(offset, count));
}
@GetMapping(SESSIONS_META_URL)
@Secured(UserRoleConstants.ADMIN)
public Response<OdinMetadata> getUserSessionsMetaData() {
log.debug("REST: UserController.getUserSessionsMetaData()");
return new Response<>(odinSessionsService.getListModel(UserSessionListDto.class));
}
@GetMapping("")
@Secured(UserRoleConstants.ADMIN)
public Response<PageableItems<UserListDto>> getAllUsers(@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "count", defaultValue = "10") int count) {
log.debug("REST: UserController.getAllUsers( {}, {} )", offset, count);
return new Response<>(userService.getAllUsers(offset, count));
}
@GetMapping("/{userId}")
@Secured(UserRoleConstants.ADMIN)
public Response<UserDto> getUser(@PathVariable Integer userId) {
log.debug("REST: UserController.getUser( {} )", userId);
return new Response<>(userService.getUserWithRolesById(userId));
}
@PostMapping("")
@Secured(UserRoleConstants.ADMIN)
public Response<UserDto> createUser(@Valid @RequestBody UserDto userDto) {
log.debug("REST: UserController.createUser( {} )", userDto.getLogin());
return new Response<>(userService.createUser(userDto));
}
@PutMapping("")
@Secured(UserRoleConstants.ADMIN)
public Response<UserDto> updateUser(@Valid @RequestBody UserDto userDto) {
log.debug("REST: UserController.updateUser( {} )", userDto.getLogin());
return new Response<>(userService.updateUser(userDto));
}
@DeleteMapping("/{userId}")
@Secured(UserRoleConstants.ADMIN)
public Response<UserDto> deleteUser(@PathVariable Integer userId) {
log.debug("REST: UserController.deleteUser( {} )", userId);
return new Response<>(userService.deleteUser(userId));
}
@PostMapping(REGISTER_URL)
public Response<UserDto> registerUser(@Valid @RequestBody UserDto userDto) {
log.debug("REST: UserController.registerUser( {} )", userDto.getLogin());
return new Response<>(userService.createUser(userDto));
}
@PostMapping(ACTIVATE_URL)
public Response<UserDto> activateUser(@RequestParam("key") String activationKey) {
log.debug("REST: UserController.activateUser( {} )", activationKey);
return new Response<>(userService.activateUser(activationKey));
}
// TODO: add page for user edit (user-profile)
@PostMapping("/change-information")
public Response<UserDto> changeInformation(@Valid @RequestBody UserDto userDto) {
log.debug("REST: UserController.changeInformation( {} )", userDto.getLogin());
return new Response<>(userService.updateUserInformation(userDto));
}
// TODO: add page for user password change (user-profile)
@PostMapping("/change-password")
public Response<UserDto> changePassword(@Valid @RequestBody UserDto userDto) {
log.debug("REST: UserController.changePassword( {} )", userDto.getLogin());
return new Response<>(userService.changeUserPassword(userDto));
}
@PostMapping(PASSWORD_RESET_REQUEST_URL)
public Response<Boolean> requestPasswordReset(@RequestParam("email") String email) {
log.debug("REST: UserController.requestPasswordReset( {} )", email);
return new Response<>(userService.requestUserPasswordReset(email));
}
@PostMapping(PASSWORD_RESET_URL)
public Response<Boolean> finishPasswordReset(@RequestParam("key") String key,
@RequestBody UserResetPasswordDto userResetPasswordDto) {
log.debug("REST: UserController.requestPasswordReset( {} )", key);
return new Response<>(userService.completeUserPasswordReset(key, userResetPasswordDto));
}
}

@ -0,0 +1,7 @@
package ru.ulstu.user.error;
public class UserActivationError extends RuntimeException {
public UserActivationError(String message) {
super(message);
}
}

@ -0,0 +1,7 @@
package ru.ulstu.user.error;
public class UserEmailExistsException extends RuntimeException {
public UserEmailExistsException(String message) {
super(message);
}
}

@ -0,0 +1,6 @@
package ru.ulstu.user.error;
public class UserIdExistsException extends RuntimeException {
public UserIdExistsException() {
}
}

@ -0,0 +1,7 @@
package ru.ulstu.user.error;
public class UserIsUndeadException extends RuntimeException {
public UserIsUndeadException(String message) {
super(message);
}
}

@ -0,0 +1,7 @@
package ru.ulstu.user.error;
public class UserLoginExistsException extends RuntimeException {
public UserLoginExistsException(String message) {
super(message);
}
}

@ -0,0 +1,6 @@
package ru.ulstu.user.error;
public class UserNotActivatedException extends RuntimeException {
public UserNotActivatedException() {
}
}

@ -0,0 +1,7 @@
package ru.ulstu.user.error;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}

@ -0,0 +1,6 @@
package ru.ulstu.user.error;
public class UserPasswordsNotValidOrNotMatchException extends RuntimeException {
public UserPasswordsNotValidOrNotMatchException() {
}
}

@ -0,0 +1,7 @@
package ru.ulstu.user.error;
public class UserResetKeyError extends RuntimeException {
public UserResetKeyError(String message) {
super(message);
}
}

@ -0,0 +1,170 @@
package ru.ulstu.user.model;
import org.hibernate.annotations.BatchSize;
import org.hibernate.validator.constraints.Email;
import ru.ulstu.configuration.Constants;
import ru.ulstu.core.model.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users")
public class User extends BaseEntity {
@NotNull
@Pattern(regexp = Constants.LOGIN_REGEX)
@Size(min = 1, max = 50)
@Column(length = 50, unique = true, nullable = false)
private String login;
@NotNull
@Size(min = 60, max = 60)
@Column(name = "password_hash", length = 60, nullable = false)
private String password;
@NotNull
@Size(max = 50)
@Column(name = "first_name", length = 50, nullable = false)
private String firstName;
@NotNull
@Size(max = 50)
@Column(name = "last_name", length = 50, nullable = false)
private String lastName;
@NotNull
@Email
@Size(min = 5, max = 100)
@Column(length = 100, nullable = false, unique = true)
private String email;
@NotNull
@Column(nullable = false)
private boolean activated;
@Size(max = 20)
@Column(name = "activation_key", length = 20)
private String activationKey;
@Column(name = "activation_date")
@Temporal(TemporalType.TIMESTAMP)
private Date activationDate;
@Size(max = 20)
@Column(name = "reset_key", length = 20)
private String resetKey;
@Column(name = "reset_date")
@Temporal(TemporalType.TIMESTAMP)
private Date resetDate;
@ManyToMany
@JoinTable(
name = "user_role",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "user_role_name", referencedColumnName = "name")})
@BatchSize(size = 20)
private Set<UserRole> roles;
public User() {
roles = new HashSet<>();
activated = false;
activationDate = new Date();
resetDate = null;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login.toLowerCase();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean getActivated() {
return activated;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
public String getActivationKey() {
return activationKey;
}
public void setActivationKey(String activationKey) {
this.activationKey = activationKey;
}
public Date getActivationDate() {
return activationDate;
}
public void setActivationDate(Date activationDate) {
this.activationDate = activationDate;
}
public String getResetKey() {
return resetKey;
}
public void setResetKey(String resetKey) {
this.resetKey = resetKey;
}
public Date getResetDate() {
return resetDate;
}
public void setResetDate(Date resetDate) {
this.resetDate = resetDate;
}
public Set<UserRole> getRoles() {
return roles;
}
public void setRoles(Collection<UserRole> roles) {
this.roles.clear();
this.roles.addAll(roles);
}
}

@ -0,0 +1,193 @@
package ru.ulstu.user.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.util.StringUtils;
import ru.ulstu.configuration.Constants;
import ru.ulstu.odin.model.OdinDto;
import ru.ulstu.odin.model.annotation.OdinCaption;
import ru.ulstu.odin.model.annotation.OdinReadOnly;
import ru.ulstu.odin.model.annotation.OdinString;
import ru.ulstu.odin.model.annotation.OdinVisible;
import ru.ulstu.user.controller.UserController;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static ru.ulstu.odin.model.annotation.OdinString.OdinStringType.PASSWORD;
public class UserDto implements OdinDto {
@OdinReadOnly
private Integer id;
@NotBlank
@Pattern(regexp = Constants.LOGIN_REGEX)
@Size(min = 4, max = 50)
@OdinCaption("Логин")
private String login;
@NotBlank
@Size(min = 2, max = 50)
@OdinCaption("Имя")
private String firstName;
@NotBlank
@Size(min = 2, max = 50)
@OdinCaption("Фамилия")
private String lastName;
@Email
@NotBlank
@Size(min = 5, max = 100)
@OdinCaption("E-Mail")
private String email;
@OdinCaption("Аккаунт активен")
private boolean activated;
@OdinCaption("Роли")
private LinkedHashSet<UserRoleDto> roles;
@OdinString(type = PASSWORD)
@OdinVisible(type = OdinVisible.OdinVisibleType.ON_UPDATE)
@OdinCaption("Текущий пароль")
@Size(max = 50)
private String oldPassword;
@OdinString(type = PASSWORD)
@OdinCaption("Пароль")
@Size(min = Constants.MIN_PASSWORD_LENGTH, max = 50)
private String password;
@OdinString(type = PASSWORD)
@OdinCaption("Пароль (подтверждение)")
@Size(min = Constants.MIN_PASSWORD_LENGTH, max = 50)
private String passwordConfirm;
public UserDto() {
activated = false;
roles = new LinkedHashSet<>();
}
public UserDto(User user) {
this();
this.id = user.getId();
this.login = user.getLogin();
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.email = user.getEmail();
this.activated = user.getActivated();
this.roles.addAll(user.getRoles().stream()
.map(UserRoleDto::new)
.collect(Collectors.toList()));
}
public Integer getId() {
return id;
}
@Override
public String getViewValue() {
return login;
}
@Override
public String getControllerPath() {
return UserController.URL;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isActivated() {
return activated;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
public Set<UserRoleDto> getRoles() {
return roles;
}
public void setRoles(Collection<UserRoleDto> roles) {
this.roles.clear();
this.roles.addAll(roles);
}
public String getOldPassword() {
return oldPassword;
}
public String getPassword() {
return password;
}
public String getPasswordConfirm() {
return passwordConfirm;
}
@JsonIgnore
public boolean isPasswordsValid() {
if (StringUtils.isEmpty(password) || StringUtils.isEmpty(passwordConfirm)) {
return false;
}
return Objects.equals(password, passwordConfirm);
}
@JsonIgnore
public boolean isOldPasswordValid() {
return !StringUtils.isEmpty(oldPassword);
}
@Override
public String toString() {
return getClass().getSimpleName() + " {" +
"id=" + id +
", login='" + login + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", activated=" + activated +
", roles=" + roles +
", password='" + password + '\'' +
", passwordConfirm='" + passwordConfirm + '\'' +
'}';
}
}

@ -0,0 +1,76 @@
package ru.ulstu.user.model;
import ru.ulstu.odin.model.OdinDto;
import ru.ulstu.odin.model.annotation.OdinCaption;
import ru.ulstu.odin.model.annotation.OdinVisible;
import ru.ulstu.user.controller.UserController;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
public class UserListDto implements OdinDto {
@OdinVisible(type = OdinVisible.OdinVisibleType.NONE)
private int id;
@OdinCaption("Логин")
private String login;
@OdinCaption("Имя")
private String firstName;
@OdinCaption("Фамилия")
private String lastName;
@OdinCaption("E-Mail")
private String email;
@OdinCaption("Аккаунт активен")
private boolean activated;
@OdinCaption("Права администратора")
private boolean admin;
public UserListDto(User user) {
this.id = user.getId();
this.login = user.getLogin();
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.email = user.getEmail();
this.activated = user.getActivated();
this.admin = Optional.ofNullable(user.getRoles()).orElse(Collections.emptySet()).stream()
.anyMatch(role -> Objects.equals(UserRoleConstants.ADMIN, role.getName()));
}
public Integer getId() {
return id;
}
@Override
public String getViewValue() {
return login;
}
@Override
public String getControllerPath() {
return UserController.URL;
}
public String getLogin() {
return login;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getEmail() {
return email;
}
public boolean isActivated() {
return activated;
}
public boolean isAdmin() {
return admin;
}
}

@ -0,0 +1,28 @@
package ru.ulstu.user.model;
import org.hibernate.validator.constraints.NotEmpty;
import ru.ulstu.configuration.Constants;
import javax.validation.constraints.Size;
import java.util.Objects;
public class UserResetPasswordDto {
@NotEmpty
@Size(min = Constants.MIN_PASSWORD_LENGTH, max = 50)
private String password;
@NotEmpty
@Size(min = Constants.MIN_PASSWORD_LENGTH, max = 50)
private String passwordConfirm;
public String getPassword() {
return password;
}
public String getPasswordConfirm() {
return passwordConfirm;
}
public boolean isPasswordsValid() {
return Objects.equals(password, passwordConfirm);
}
}

@ -0,0 +1,46 @@
package ru.ulstu.user.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Entity
@Table(name = "user_roles")
public class UserRole {
@Id
@NotNull
@Size(max = 50)
@Column(length = 50, nullable = false)
private String name;
public UserRole() {
}
public UserRole(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UserRole role = (UserRole) o;
return !(name != null ? !name.equals(role.name) : role.name != null);
}
@Override
public int hashCode() {
return name != null ? name.hashCode() : 0;
}
}

@ -0,0 +1,6 @@
package ru.ulstu.user.model;
public class UserRoleConstants {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
}

@ -0,0 +1,36 @@
package ru.ulstu.user.model;
import ru.ulstu.odin.model.OdinDto;
import ru.ulstu.odin.model.annotation.OdinCaption;
import ru.ulstu.user.controller.UserController;
public class UserRoleDto implements OdinDto {
@OdinCaption("Роль")
private String id;
public UserRoleDto() {
}
public UserRoleDto(UserRole role) {
this.id = role.getName();
}
public UserRoleDto(String name) {
this.id = name;
}
@Override
public String getId() {
return id;
}
@Override
public String getViewValue() {
return id;
}
@Override
public String getControllerPath() {
return UserController.URL + UserController.ROLES_URL;
}
}

@ -0,0 +1,75 @@
package ru.ulstu.user.model;
import ru.ulstu.core.model.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.Date;
@Entity
@Table(name = "user_sessions")
public class UserSession extends BaseEntity {
@NotNull
@Column(name = "session_id", nullable = false, unique = true)
private String sessionId;
@NotNull
@Column(name = "ip_address", nullable = false)
private String ipAddress;
@NotNull
@Column(nullable = false)
private String host;
@NotNull
@Column(name = "login_time", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
private Date loginTime;
@Column(name = "logout_time")
@Temporal(TemporalType.TIMESTAMP)
private Date logoutTime;
@ManyToOne(optional = false)
@JoinColumn(name = "user_id")
private User user;
public UserSession() {
}
public UserSession(String sessionId, String ipAddress, String host, User user) {
this.sessionId = sessionId;
this.ipAddress = ipAddress;
this.host = host;
this.loginTime = new Date();
this.user = user;
}
public String getSessionId() {
return sessionId;
}
public String getIpAddress() {
return ipAddress;
}
public String getHost() {
return host;
}
public Date getLoginTime() {
return loginTime;
}
public Date getLogoutTime() {
return logoutTime;
}
public User getUser() {
return user;
}
public void close() {
this.logoutTime = new Date();
}
}

@ -0,0 +1,56 @@
package ru.ulstu.user.model;
import ru.ulstu.odin.model.annotation.OdinCaption;
import ru.ulstu.odin.model.annotation.OdinDate;
import java.util.Date;
public class UserSessionListDto {
@OdinCaption("Сессия")
private String sessionId;
@OdinCaption("Пользователь")
private String login;
@OdinCaption("IP адрес")
private String ipAddress;
@OdinCaption("Хост")
private String host;
@OdinCaption("Вход")
@OdinDate(type = OdinDate.OdinDateType.DATETIME)
private Date loginTime;
@OdinCaption("Выход")
@OdinDate(type = OdinDate.OdinDateType.DATETIME)
private Date logoutTime;
public UserSessionListDto(UserSession userSession) {
this.sessionId = userSession.getSessionId();
this.login = userSession.getUser().getLogin();
this.ipAddress = userSession.getIpAddress();
this.host = userSession.getHost();
this.loginTime = userSession.getLoginTime();
this.logoutTime = userSession.getLogoutTime();
}
public String getSessionId() {
return sessionId;
}
public String getLogin() {
return login;
}
public String getIpAddress() {
return ipAddress;
}
public String getHost() {
return host;
}
public Date getLoginTime() {
return loginTime;
}
public Date getLogoutTime() {
return logoutTime;
}
}

@ -0,0 +1,28 @@
package ru.ulstu.user.repository;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.user.model.User;
import java.util.Date;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Integer> {
User findOneByActivationKey(String activationKey);
List<User> findAllByActivatedIsFalseAndActivationDateBefore(Date date);
User findOneByResetKey(String resetKey);
List<User> findAllByResetKeyNotNullAndResetDateBefore(Date date);
User findOneByEmailIgnoreCase(String email);
User findOneByLoginIgnoreCase(String login);
@EntityGraph(attributePaths = "roles")
User findOneWithRolesById(int id);
@EntityGraph(attributePaths = "roles")
User findOneWithRolesByLogin(String login);
}

@ -0,0 +1,7 @@
package ru.ulstu.user.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.user.model.UserRole;
public interface UserRoleRepository extends JpaRepository<UserRole, String> {
}

@ -0,0 +1,13 @@
package ru.ulstu.user.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.ulstu.user.model.UserSession;
import java.util.Date;
import java.util.List;
public interface UserSessionRepository extends JpaRepository<UserSession, Integer> {
UserSession findOneBySessionId(String sessionId);
List<UserSession> findAllByLogoutTimeIsNullAndLoginTimeBefore(Date date);
}

@ -0,0 +1,50 @@
package ru.ulstu.user.scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import ru.ulstu.core.util.DateUtils;
import ru.ulstu.user.model.User;
import ru.ulstu.user.repository.UserRepository;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
@Service
public class UserScheduler {
private final Logger log = LoggerFactory.getLogger(UserScheduler.class);
private final UserRepository userRepository;
public UserScheduler(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Scheduled(cron = "0 0 1 * * ?")
public void removeNotActivatedUsers() {
log.debug("UserScheduler.removeNotActivatedUsers started");
List<User> users = userRepository.findAllByActivatedIsFalseAndActivationDateBefore(
DateUtils.instantToDate(Instant.now().minus(3, ChronoUnit.DAYS)));
users.forEach(user -> {
log.debug("Deleting not activated user {}", user.getLogin());
userRepository.delete(user);
});
log.debug("UserScheduler.removeNotActivatedUsers finished");
}
@Scheduled(cron = "0 0 1 * * ?")
public void removeUnusedRestKeyRequestsOfUsers() {
log.debug("UserScheduler.removeUnusedRestKeyRequestsOfUsers started");
List<User> users = userRepository.findAllByResetKeyNotNullAndResetDateBefore(
DateUtils.instantToDate(Instant.now().minus(7, ChronoUnit.DAYS)));
users.forEach(user -> {
log.debug("Deleting old reset key request of user {}", user.getLogin());
user.setResetKey(null);
user.setResetDate(null);
userRepository.save(user);
});
log.debug("UserScheduler.removeUnusedRestKeyRequestsOfUsers finished");
}
}

@ -0,0 +1,37 @@
package ru.ulstu.user.scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import ru.ulstu.core.util.DateUtils;
import ru.ulstu.user.model.UserSession;
import ru.ulstu.user.repository.UserSessionRepository;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
@Service
public class UserSessionScheduler {
private final Logger log = LoggerFactory.getLogger(UserSessionScheduler.class);
private final UserSessionRepository userSessionRepository;
public UserSessionScheduler(UserSessionRepository userSessionRepository) {
this.userSessionRepository = userSessionRepository;
}
@Scheduled(cron = "0 0 1 * * ?")
public void closeOldSessions() {
log.debug("UserSessionScheduler.closeOldSessions started");
final List<UserSession> sessions = userSessionRepository.findAllByLogoutTimeIsNullAndLoginTimeBefore(
DateUtils.instantToDate(Instant.now().minus(1, ChronoUnit.DAYS)));
sessions.forEach(session -> {
log.debug("Close session {}", session.getSessionId());
session.close();
userSessionRepository.save(session);
});
log.debug("UserSessionScheduler.closeOldSessions finished");
}
}

@ -0,0 +1,78 @@
package ru.ulstu.user.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring4.SpringTemplateEngine;
import ru.ulstu.configuration.ApplicationProperties;
import ru.ulstu.configuration.Constants;
import ru.ulstu.user.model.User;
import javax.mail.internet.MimeMessage;
import java.nio.charset.StandardCharsets;
@Service
public class MailService {
private final Logger log = LoggerFactory.getLogger(MailService.class);
private static final String USER = "user";
private static final String BASE_URL = "baseUrl";
private final JavaMailSender javaMailSender;
private final SpringTemplateEngine templateEngine;
private final MailProperties mailProperties;
private final ApplicationProperties applicationProperties;
public MailService(JavaMailSender javaMailSender, SpringTemplateEngine templateEngine,
MailProperties mailProperties, ApplicationProperties applicationProperties) {
this.javaMailSender = javaMailSender;
this.templateEngine = templateEngine;
this.mailProperties = mailProperties;
this.applicationProperties = applicationProperties;
}
@Async
public void sendEmail(String to, String subject, String content) {
log.debug("Send email to '{}' with subject '{}'", to, subject);
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
try {
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, false, StandardCharsets.UTF_8.name());
message.setTo(to);
message.setFrom(mailProperties.getUsername());
message.setSubject(subject);
message.setText(content, true);
javaMailSender.send(mimeMessage);
log.debug("Sent email to User '{}'", to);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.warn("Email could not be sent to user '{}'", to, e);
} else {
log.warn("Email could not be sent to user '{}': {}", to, e.getMessage());
}
}
}
@Async
public void sendEmailFromTemplate(User user, String templateName, String subject) {
Context context = new Context();
context.setVariable(USER, user);
context.setVariable(BASE_URL, applicationProperties.getBaseUrl());
String content = templateEngine.process(templateName, context);
sendEmail(user.getEmail(), subject, content);
}
@Async
public void sendActivationEmail(User user) {
sendEmailFromTemplate(user, "activationEmail", Constants.MAIL_ACTIVATE);
}
@Async
public void sendPasswordResetMail(User user) {
sendEmailFromTemplate(user, "passwordResetEmail", Constants.MAIL_RESET);
}
}

@ -0,0 +1,66 @@
package ru.ulstu.user.service;
import org.springframework.stereotype.Service;
import ru.ulstu.user.model.*;
import ru.ulstu.user.model.*;
import ru.ulstu.user.repository.UserRoleRepository;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class UserMapper {
private final UserRoleRepository userRoleRepository;
public UserMapper(UserRoleRepository userRoleRepository) {
this.userRoleRepository = userRoleRepository;
}
public Set<UserRole> rolesFromDto(Set<UserRoleDto> strings) {
return Optional.ofNullable(strings).orElse(Collections.emptySet()).stream()
.filter(Objects::nonNull)
.map(role -> userRoleRepository.findOne(role.getId().toString()))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
public UserDto userEntityToUserDto(User userEntity) {
if (userEntity == null) {
return null;
}
return new UserDto(userEntity);
}
public UserListDto userEntityToUserListDto(User userEntity) {
if (userEntity == null) {
return null;
}
return new UserListDto(userEntity);
}
public List<UserListDto> userEntitiesToUserListDtos(List<User> userEntities) {
return userEntities.stream()
.filter(Objects::nonNull)
.map(this::userEntityToUserListDto)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public User userDtoToUserEntity(UserDto userDto) {
if (userDto == null) {
return null;
}
final User user = new User();
user.setId(userDto.getId());
user.setLogin(userDto.getLogin());
user.setFirstName(userDto.getFirstName());
user.setLastName(userDto.getLastName());
user.setEmail(userDto.getEmail());
user.setActivated(userDto.isActivated());
final Set<UserRole> roles = this.rolesFromDto(userDto.getRoles());
if (!roles.isEmpty()) {
user.setRoles(roles);
}
return user;
}
}

@ -0,0 +1,285 @@
package ru.ulstu.user.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import ru.ulstu.configuration.ApplicationProperties;
import ru.ulstu.core.error.EntityIdIsNullException;
import ru.ulstu.core.jpa.OffsetablePageRequest;
import ru.ulstu.core.model.BaseEntity;
import ru.ulstu.core.model.response.PageableItems;
import ru.ulstu.user.error.*;
import ru.ulstu.user.model.*;
import ru.ulstu.user.repository.UserRepository;
import ru.ulstu.user.repository.UserRoleRepository;
import ru.ulstu.user.util.UserUtils;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Transactional
public class UserService implements UserDetailsService {
private final Logger log = LoggerFactory.getLogger(UserService.class);
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final UserRoleRepository userRoleRepository;
private final UserMapper userMapper;
private final MailService mailService;
private final ApplicationProperties applicationProperties;
public UserService(UserRepository userRepository,
PasswordEncoder passwordEncoder,
UserRoleRepository userRoleRepository,
UserMapper userMapper,
MailService mailService,
ApplicationProperties applicationProperties) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.userRoleRepository = userRoleRepository;
this.userMapper = userMapper;
this.mailService = mailService;
this.applicationProperties = applicationProperties;
}
private User getUserByEmail(String email) {
return userRepository.findOneByEmailIgnoreCase(email);
}
private User getUserByActivationKey(String activationKey) {
return userRepository.findOneByActivationKey(activationKey);
}
public User getUserByLogin(String login) {
return userRepository.findOneByLoginIgnoreCase(login);
}
@Transactional(readOnly = true)
public UserDto getUserWithRolesById(Integer userId) {
final User userEntity = userRepository.findOneWithRolesById(userId);
if (userEntity == null) {
throw new UserNotFoundException(userId.toString());
}
return userMapper.userEntityToUserDto(userEntity);
}
@Transactional(readOnly = true)
public PageableItems<UserListDto> getAllUsers(int offset, int count) {
final Page<User> page = userRepository.findAll(new OffsetablePageRequest(offset, count, new Sort("id")));
return new PageableItems<>(page.getTotalElements(), userMapper.userEntitiesToUserListDtos(page.getContent()));
}
@Transactional(readOnly = true)
public PageableItems<UserRoleDto> getUserRoles() {
final List<UserRoleDto> roles = userRoleRepository.findAll().stream()
.map(UserRoleDto::new)
.sorted(Comparator.comparing(UserRoleDto::getViewValue))
.collect(Collectors.toList());
return new PageableItems<>(roles.size(), roles);
}
public UserDto createUser(UserDto userDto) {
if (userDto.getId() != null) {
throw new UserIdExistsException();
}
if (getUserByLogin(userDto.getLogin()) != null) {
throw new UserLoginExistsException(userDto.getLogin());
}
if (getUserByEmail(userDto.getEmail()) != null) {
throw new UserEmailExistsException(userDto.getEmail());
}
if (!userDto.isPasswordsValid()) {
throw new UserPasswordsNotValidOrNotMatchException();
}
User user = userMapper.userDtoToUserEntity(userDto);
user.setActivated(false);
user.setActivationKey(UserUtils.generateActivationKey());
user.setRoles(Collections.singleton(new UserRole(UserRoleConstants.USER)));
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
user = userRepository.save(user);
mailService.sendActivationEmail(user);
log.debug("Created Information for User: {}", user.getLogin());
return userMapper.userEntityToUserDto(user);
}
public UserDto activateUser(String activationKey) {
final User user = getUserByActivationKey(activationKey);
if (user == null) {
throw new UserActivationError(activationKey);
}
user.setActivated(true);
user.setActivationKey(null);
user.setActivationDate(null);
log.debug("Activated user: {}", user.getLogin());
return userMapper.userEntityToUserDto(userRepository.save(user));
}
public UserDto updateUser(UserDto userDto) {
if (userDto.getId() == null) {
throw new EntityIdIsNullException();
}
if (!Objects.equals(
Optional.ofNullable(getUserByEmail(userDto.getEmail()))
.map(BaseEntity::getId).orElse(userDto.getId()),
userDto.getId())) {
throw new UserEmailExistsException(userDto.getEmail());
}
if (!Objects.equals(
Optional.ofNullable(getUserByLogin(userDto.getLogin()))
.map(BaseEntity::getId).orElse(userDto.getId()),
userDto.getId())) {
throw new UserLoginExistsException(userDto.getLogin());
}
User user = userRepository.findOne(userDto.getId());
if (user == null) {
throw new UserNotFoundException(userDto.getId().toString());
}
if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) {
userDto.setLogin(applicationProperties.getUndeadUserLogin());
userDto.setActivated(true);
userDto.setRoles(Collections.singletonList(new UserRoleDto(UserRoleConstants.ADMIN)));
}
user.setLogin(userDto.getLogin());
user.setFirstName(userDto.getFirstName());
user.setLastName(userDto.getLastName());
user.setEmail(userDto.getEmail());
if (userDto.isActivated() != user.getActivated()) {
if (userDto.isActivated()) {
user.setActivationKey(null);
user.setActivationDate(null);
} else {
user.setActivationKey(UserUtils.generateActivationKey());
user.setActivationDate(new Date());
}
}
user.setActivated(userDto.isActivated());
final Set<UserRole> roles = userMapper.rolesFromDto(userDto.getRoles());
user.setRoles(roles.isEmpty()
? Collections.singleton(new UserRole(UserRoleConstants.USER))
: roles);
if (!StringUtils.isEmpty(userDto.getOldPassword())) {
if (!userDto.isPasswordsValid() || !userDto.isOldPasswordValid()) {
throw new UserPasswordsNotValidOrNotMatchException();
}
if (!passwordEncoder.matches(userDto.getOldPassword(), user.getPassword())) {
throw new UserPasswordsNotValidOrNotMatchException();
}
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
log.debug("Changed password for User: {}", user.getLogin());
}
user = userRepository.save(user);
log.debug("Changed Information for User: {}", user.getLogin());
return userMapper.userEntityToUserDto(user);
}
public UserDto updateUserInformation(UserDto userDto) {
if (userDto.getId() == null) {
throw new EntityIdIsNullException();
}
if (!Objects.equals(
Optional.ofNullable(getUserByEmail(userDto.getEmail()))
.map(BaseEntity::getId).orElse(userDto.getId()),
userDto.getId())) {
throw new UserEmailExistsException(userDto.getEmail());
}
User user = userRepository.findOne(userDto.getId());
if (user == null) {
throw new UserNotFoundException(userDto.getId().toString());
}
user.setFirstName(userDto.getFirstName());
user.setLastName(userDto.getLastName());
user.setEmail(userDto.getEmail());
user = userRepository.save(user);
log.debug("Updated Information for User: {}", user.getLogin());
return userMapper.userEntityToUserDto(user);
}
public UserDto changeUserPassword(UserDto userDto) {
if (userDto.getId() == null) {
throw new EntityIdIsNullException();
}
if (!userDto.isPasswordsValid() || !userDto.isOldPasswordValid()) {
throw new UserPasswordsNotValidOrNotMatchException();
}
final String login = UserUtils.getCurrentUserLogin();
final User user = userRepository.findOneByLoginIgnoreCase(login);
if (user == null) {
throw new UserNotFoundException(login);
}
if (!passwordEncoder.matches(userDto.getOldPassword(), user.getPassword())) {
throw new UserPasswordsNotValidOrNotMatchException();
}
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
log.debug("Changed password for User: {}", user.getLogin());
return userMapper.userEntityToUserDto(userRepository.save(user));
}
public boolean requestUserPasswordReset(String email) {
User user = userRepository.findOneByEmailIgnoreCase(email);
if (user == null) {
throw new UserNotFoundException(email);
}
if (!user.getActivated()) {
throw new UserNotActivatedException();
}
user.setResetKey(UserUtils.generateResetKey());
user.setResetDate(new Date());
user = userRepository.save(user);
mailService.sendPasswordResetMail(user);
log.debug("Created Reset Password Request for User: {}", user.getLogin());
return true;
}
public boolean completeUserPasswordReset(String key, UserResetPasswordDto userResetPasswordDto) {
if (!userResetPasswordDto.isPasswordsValid()) {
throw new UserPasswordsNotValidOrNotMatchException();
}
User user = userRepository.findOneByResetKey(key);
if (user == null) {
throw new UserResetKeyError(key);
}
user.setPassword(passwordEncoder.encode(userResetPasswordDto.getPassword()));
user.setResetKey(null);
user.setResetDate(null);
user = userRepository.save(user);
log.debug("Reset Password for User: {}", user.getLogin());
return true;
}
public UserDto deleteUser(Integer userId) {
final User user = userRepository.findOne(userId);
if (user == null) {
throw new UserNotFoundException(userId.toString());
}
if (applicationProperties.getUndeadUserLogin().equalsIgnoreCase(user.getLogin())) {
throw new UserIsUndeadException(user.getLogin());
}
userRepository.delete(user);
log.debug("Deleted User: {}", user.getLogin());
return userMapper.userEntityToUserDto(user);
}
@Override
public UserDetails loadUserByUsername(String username) {
final User user = userRepository.findOneByLoginIgnoreCase(username);
if (user == null) {
throw new UserNotFoundException(username);
}
if (!user.getActivated()) {
throw new UserNotActivatedException();
}
return new org.springframework.security.core.userdetails.User(user.getLogin(),
user.getPassword(),
Optional.ofNullable(user.getRoles()).orElse(Collections.emptySet()).stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList()));
}
}

@ -0,0 +1,57 @@
package ru.ulstu.user.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.ulstu.core.model.response.PageableItems;
import ru.ulstu.core.jpa.OffsetablePageRequest;
import ru.ulstu.user.error.UserNotFoundException;
import ru.ulstu.user.model.User;
import ru.ulstu.user.model.UserSession;
import ru.ulstu.user.model.UserSessionListDto;
import ru.ulstu.user.repository.UserSessionRepository;
import static ru.ulstu.core.util.StreamApiUtils.convert;
@Service
@Transactional
public class UserSessionService {
private final Logger log = LoggerFactory.getLogger(UserSessionService.class);
private final UserSessionRepository userSessionRepository;
private final UserService userService;
public UserSessionService(UserSessionRepository userSessionRepository, UserService userService) {
this.userSessionRepository = userSessionRepository;
this.userService = userService;
}
@Transactional(readOnly = true)
public PageableItems<UserSessionListDto> getSessions(int offset, int count) {
final Page<UserSession> page = userSessionRepository.findAll(
new OffsetablePageRequest(offset, count, new Sort(Sort.Direction.DESC, "loginTime")));
return new PageableItems<>(page.getTotalElements(),
convert(page.getContent(), UserSessionListDto::new));
}
public void createUserSession(String sessionId, String login, String ipAddress, String host) {
final User user = userService.getUserByLogin(login);
if (user == null) {
throw new UserNotFoundException(login);
}
userSessionRepository.save(new UserSession(sessionId, ipAddress, host, user));
log.debug("User session {} created for user {}@{} ({})", sessionId, login, ipAddress, host);
}
public void closeUserSession(String sessionId) {
final UserSession userSession = userSessionRepository.findOneBySessionId(sessionId);
if (userSession == null) {
throw new IllegalArgumentException(String.format("User session %s not found", sessionId));
}
userSession.close();
userSessionRepository.save(userSession);
log.debug("User session {} closed", sessionId);
}
}

@ -0,0 +1,35 @@
package ru.ulstu.user.util;
import org.apache.commons.lang3.RandomStringUtils;
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.UserDetails;
public class UserUtils {
private static final int DEF_COUNT = 20;
public static String generateActivationKey() {
return RandomStringUtils.randomNumeric(DEF_COUNT);
}
public static String generateResetKey() {
return RandomStringUtils.randomNumeric(DEF_COUNT);
}
public static String getCurrentUserLogin() {
final SecurityContext securityContext = SecurityContextHolder.getContext();
if (securityContext == null) {
return null;
}
final Authentication authentication = securityContext.getAuthentication();
if (authentication.getPrincipal() instanceof UserDetails) {
final UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
}
if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
}
}

@ -0,0 +1,36 @@
# Server Settings
spring.main.banner-mode=off
server.port=8443
server.http.port=8080
spring.http.multipart.maxFileSize=20MB
spring.http.multipart.maxRequestSize=20MB
# Thymeleaf Settings
spring.thymeleaf.cache=false
# SSL Settings
security.require-ssl=true
server.ssl.key-store=classpath:sample.jks
server.ssl.key-store-password=secret
server.ssl.key-password=password
# Log settings (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF)
logging.level.ru.ulstu=DEBUG
# Mail Settings
spring.mail.host=smtp.yandex.ru
spring.mail.port=465
spring.mail.username=balance@soft.kitchen
spring.mail.password=fkvfpbalance
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
# JPA Settings
spring.datasource.url=jdbc:postgresql://localhost:5432/ng-tracker
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driverclassName=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=validate
# Liquibase Settings
liquibase.drop-first=false
liquibase.enabled=true
liquibase.change-log=classpath:db/changelog-master.xml
# Application Settings
ng-tracker.base-url=https://127.0.0.1:8443
ng-tracker.undead-user-login=admin

@ -0,0 +1,489 @@
Anton Romanov;Thu Mar 15 11:10:34 2018 +0400;change to date time
romanov73;Thu Mar 15 11:03:19 2018 +0400;read commits in constructor
romanov73;Thu Mar 15 09:12:07 2018 +0400;add commits page
Romanov Anton;Wed Mar 14 20:26:54 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 20:16:53 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 20:08:09 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 20:00:58 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 19:50:22 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 19:34:17 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 19:20:42 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 18:59:18 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 18:47:17 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 18:35:37 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 18:33:54 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 18:29:27 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 18:21:18 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 18:19:27 2018 +0000;Update README.md
Romanov Anton;Wed Mar 14 18:16:06 2018 +0000;Update README.md
romanov73;Wed Mar 14 21:28:10 2018 +0400;fix using constant
Aleksey Filippov;Wed Mar 14 20:07:25 2018 +0400;some fixes after merge
Aleksey Filippov;Wed Mar 14 19:52:45 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into odin-ui
Aleksey Filippov;Wed Mar 14 19:48:45 2018 +0400;refactoring of odin paginator
romanov73;Wed Mar 14 18:43:16 2018 +0400;Merge branch 'develop' into 36-rest
romanov73;Wed Mar 14 18:17:02 2018 +0400;Merge branch 'develop' into 36-rest
Aleksey Filippov;Wed Mar 14 18:11:00 2018 +0400;fix null value check in formatter
Aleksey Filippov;Wed Mar 14 18:06:16 2018 +0400;fix odin paginator style, fix odin negative file
romanov73;Wed Mar 14 18:04:47 2018 +0400;rename entities
Aleksey Filippov;Wed Mar 14 17:58:59 2018 +0400;improve odin table look
romanov73;Wed Mar 14 17:47:45 2018 +0400;fix db changelogs
romanov73;Wed Mar 14 17:47:20 2018 +0400;fix db changelogs
Gleb;Wed Mar 14 08:50:23 2018 +0000;Merge branch '48-' into 'develop'
funny73;Wed Mar 14 12:28:55 2018 +0400;Поправил отображение подразделения при редактировании смены
Aleksey Filippov;Wed Mar 14 00:02:24 2018 +0400;move navbar to the left, move odin css to separate file
Aleksey Filippov;Tue Mar 13 23:26:33 2018 +0400;some style fixes
Aleksey Filippov;Tue Mar 13 23:22:37 2018 +0400;move version panel to navbar
funny73;Tue Mar 13 20:41:41 2018 +0400;Поправил валидацию сменности
romanov73;Tue Mar 13 17:26:54 2018 +0400;fix offsetable page request
romanov73;Tue Mar 13 17:26:02 2018 +0400;fix table constructor
romanov73;Tue Mar 13 17:24:56 2018 +0400;fix checking empty value
Romanov Anton;Tue Mar 13 13:09:41 2018 +0000;Merge branch 'odin-ui' into '36-rest'
Aleksey Filippov;Tue Mar 13 16:59:27 2018 +0400;notes updated
Aleksey Filippov;Tue Mar 13 16:56:00 2018 +0400;odin refactoring
Aleksey Filippov;Tue Mar 13 16:53:38 2018 +0400;odin refactoring
romanov73;Tue Mar 13 15:27:17 2018 +0400;fix tool load calc
romanov73;Tue Mar 13 14:46:46 2018 +0400;fix area load calc
Aleksey Filippov;Tue Mar 13 13:38:23 2018 +0400;some fixes
romanov73;Tue Mar 13 13:33:25 2018 +0400;Merge remote-tracking branch 'origin/develop' into develop
romanov73;Tue Mar 13 13:33:07 2018 +0400;fix tools count calc
Aleksey Filippov;Tue Mar 13 13:12:09 2018 +0400;some odin refactoring
Aleksey Filippov;Tue Mar 13 13:11:41 2018 +0400;some offsetablepagerequest fix
Aleksey Filippov;Tue Mar 13 13:06:48 2018 +0400;add offsetablepagerequest
Aleksey Filippov;Tue Mar 13 07:27:12 2018 +0000;Merge branch '51-rest-points' into '36-rest'
romanov73;Tue Mar 13 11:22:02 2018 +0400;fix by comment: remove default version id
romanov73;Tue Mar 13 00:04:50 2018 +0400;fix path
romanov73;Mon Mar 12 23:55:05 2018 +0400;add tool square dictionary
romanov73;Mon Mar 12 23:36:49 2018 +0400;add dictionary pages
romanov73;Mon Mar 12 23:18:01 2018 +0400;sort second level menu items by name
romanov73;Mon Mar 12 23:11:22 2018 +0400;fix menu
romanov73;Mon Mar 12 22:52:15 2018 +0400;add stream api utils and converter for "map ... collect"
funny73;Mon Mar 12 20:01:47 2018 +0400;Добавил свойство базового изделия для изделия
romanov73;Mon Mar 12 19:46:52 2018 +0400;refactor units
romanov73;Mon Mar 12 19:36:57 2018 +0400;refactor tool types and work types
romanov73;Mon Mar 12 19:20:23 2018 +0400;refactor tools
romanov73;Mon Mar 12 19:07:09 2018 +0400;refactor stages
romanov73;Mon Mar 12 18:53:26 2018 +0400;refactor positions
romanov73;Mon Mar 12 18:27:36 2018 +0400;refactor employees
romanov73;Mon Mar 12 18:26:49 2018 +0400;refactor employees
romanov73;Mon Mar 12 18:09:38 2018 +0400;refactor categories
Aleksey Filippov;Mon Mar 12 17:32:06 2018 +0400;todo added
Aleksey Filippov;Mon Mar 12 17:29:51 2018 +0400;paginator added to odin
Aleksey Filippov;Mon Mar 12 17:29:06 2018 +0400;notes updated
funny73;Mon Mar 12 16:29:13 2018 +0400;Добавил сущность изделие без привязки к производственной программе
funny73;Mon Mar 12 15:16:35 2018 +0400;Переименовал сущность Product на ProductOnProgram Ещё немного переименования
funny73;Mon Mar 12 14:39:52 2018 +0400;Переименовал сущность Product на ProductOnProgram
Aleksey Filippov;Mon Mar 12 13:44:42 2018 +0400;add formatters and initial form support to odin
Aleksey Filippov;Mon Mar 12 13:44:16 2018 +0400;userlistdto refactoring
Aleksey Filippov;Mon Mar 12 13:43:59 2018 +0400;dateutils improvements
Aleksey Filippov;Mon Mar 12 13:43:43 2018 +0400;add support of localdatetime type to odin
Aleksey Filippov;Mon Mar 12 13:42:54 2018 +0400;odin backend example added
Aleksey Filippov;Mon Mar 12 13:42:12 2018 +0400;notes updated
funny73;Mon Mar 12 12:16:02 2018 +0400;Поправил всплывающие сообщения при работе с "Категориями"
Aleksey Filippov;Mon Mar 12 11:25:51 2018 +0400;some refactoring
Aleksey Filippov;Mon Mar 12 11:25:30 2018 +0400;odinid annotation added
Aleksey Filippov;Sun Mar 11 15:23:49 2018 +0400;notes updated
Aleksey Filippov;Sun Mar 11 15:18:45 2018 +0400;some fixes
Aleksey Filippov;Sun Mar 11 15:09:57 2018 +0400;simple table draw support added, some refactoring
Aleksey Filippov;Sun Mar 11 15:08:22 2018 +0400;add user id field to userlistdto
Aleksey Filippov;Sun Mar 11 14:32:50 2018 +0400;add jsonproperty annotation support
Aleksey Filippov;Sun Mar 11 13:38:45 2018 +0400;some template fixes
Aleksey Filippov;Sun Mar 11 13:33:09 2018 +0400;some balance page fixes
Aleksey Filippov;Sun Mar 11 13:25:58 2018 +0400;add jsonignore annotation support
Aleksey Filippov;Thu Mar 8 14:16:20 2018 +0400;some odin improvements
Aleksey Filippov;Wed Mar 7 16:03:04 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
Aleksey Filippov;Wed Mar 7 16:01:21 2018 +0400;odin submodule for basic types added, some refactoring
romanov73;Tue Mar 6 22:52:02 2018 +0400;remove old packages
romanov73;Tue Mar 6 19:12:27 2018 +0400;add tool type crud
romanov73;Tue Mar 6 13:09:51 2018 +0400;add version crud
romanov73;Tue Mar 6 10:48:36 2018 +0400;add unit crud
romanov73;Mon Mar 5 16:58:37 2018 +0400;fix tree
romanov73;Mon Mar 5 16:38:37 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
romanov73;Mon Mar 5 16:38:21 2018 +0400;employee crud
Aleksey Filippov;Mon Mar 5 15:43:49 2018 +0400;some fixes
Aleksey Filippov;Mon Mar 5 15:39:53 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
Aleksey Filippov;Mon Mar 5 15:39:33 2018 +0400;user reset password function added, some refactoring
romanov73;Mon Mar 5 15:29:16 2018 +0400;fix delete category
Aleksey Filippov;Mon Mar 5 15:08:27 2018 +0400;user change password function added, some refactoring
romanov73;Mon Mar 5 15:08:17 2018 +0400;add category crud
romanov73;Mon Mar 5 14:07:10 2018 +0400;add tree component
Aleksey Filippov;Mon Mar 5 11:30:22 2018 +0400;user delete function added, some refactoring
Aleksey Filippov;Mon Mar 5 11:18:23 2018 +0400;user update function added
Aleksey Filippov;Mon Mar 5 11:18:06 2018 +0400;some refactoring
Aleksey Filippov;Mon Mar 5 10:06:04 2018 +0400;add user activation function
Aleksey Filippov;Mon Mar 5 09:48:22 2018 +0400;add scheduler for users
Aleksey Filippov;Mon Mar 5 09:48:07 2018 +0400;add activateddate field to userentity
Aleksey Filippov;Mon Mar 5 09:47:25 2018 +0400;notes file updated
Aleksey Filippov;Mon Mar 5 09:47:11 2018 +0400;some refactoring
Aleksey Filippov;Mon Mar 5 09:21:49 2018 +0400;thymeleaf cache settings added
Aleksey Filippov;Mon Mar 5 09:21:11 2018 +0400;notes file added
Aleksey Filippov;Mon Mar 5 09:20:57 2018 +0400;some refactoring
Aleksey Filippov;Mon Mar 5 08:37:01 2018 +0400;some refactoring
romanov73;Mon Mar 5 00:08:02 2018 +0400;add callbacks on version getters
romanov73;Mon Mar 5 00:07:34 2018 +0400;fix npe
romanov73;Sun Mar 4 01:13:57 2018 +0400;fix column style
romanov73;Sun Mar 4 01:06:38 2018 +0400;fix table style
romanov73;Sun Mar 4 00:27:14 2018 +0400;add balance dto + draw employee balance
romanov73;Sat Mar 3 22:43:06 2018 +0400;add balance services
romanov73;Sat Mar 3 16:31:19 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
romanov73;Sat Mar 3 16:31:04 2018 +0400;add employee balance table
Aleksey Filippov;Sat Mar 3 15:54:23 2018 +0400;some user support improvements
Aleksey Filippov;Sat Mar 3 15:53:20 2018 +0400;edit migration files, need manual fix of databasechangelog table
Aleksey Filippov;Sat Mar 3 15:51:58 2018 +0400;add application properties handler
Aleksey Filippov;Sat Mar 3 15:51:19 2018 +0400;disable tests
Aleksey Filippov;Sat Mar 3 13:49:32 2018 +0400;some refactoring
romanov73;Fri Mar 2 18:10:23 2018 +0400;add balance page
Aleksey Filippov;Fri Mar 2 18:04:39 2018 +0400;some refactoring
Aleksey Filippov;Fri Mar 2 17:51:41 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
Aleksey Filippov;Fri Mar 2 17:50:59 2018 +0400;advicecontroller improvements
Aleksey Filippov;Fri Mar 2 17:50:39 2018 +0400;some mvc improvements
romanov73;Fri Mar 2 15:15:46 2018 +0400;add dtos
romanov73;Fri Mar 2 15:02:38 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
Aleksey Filippov;Fri Mar 2 14:54:33 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
Aleksey Filippov;Fri Mar 2 14:53:39 2018 +0400;add migrations
Aleksey Filippov;Fri Mar 2 14:53:30 2018 +0400;some core improvements
Aleksey Filippov;Fri Mar 2 14:53:13 2018 +0400;some users improvements
romanov73;Fri Mar 2 12:42:54 2018 +0400;add category service
romanov73;Fri Mar 2 12:37:51 2018 +0400;fix unit service
romanov73;Fri Mar 2 11:24:11 2018 +0400;add unit controller
romanov73;Thu Mar 1 23:19:42 2018 +0400;fix versions panel
romanov73;Thu Mar 1 22:50:57 2018 +0400;Merge branch 'develop' into 36-rest
romanov73;Thu Mar 1 22:50:37 2018 +0400;Merge branch 'develop' into 36-rest
Romanov Anton;Thu Mar 1 18:31:03 2018 +0000;Merge branch '29-' into 'develop'
romanov73;Thu Mar 1 22:25:49 2018 +0400;change versions
romanov73;Thu Mar 1 20:14:22 2018 +0400;show version select
romanov73;Thu Mar 1 19:56:24 2018 +0400;add old models, add versions controller
romanov73;Thu Mar 1 19:55:59 2018 +0400;add old models, add versions controller
romanov73;Thu Mar 1 19:04:47 2018 +0400;save menu to session
romanov73;Thu Mar 1 18:50:00 2018 +0400;add favicon
funny73;Thu Mar 1 18:01:43 2018 +0400;1) Поправил имена колонок и таблицы для смен под постгрес 2) Исправил ошибку в имени переменной thidShift ->thirdShift
romanov73;Thu Mar 1 16:03:03 2018 +0400;add menu and restore changelogs
funny73;Thu Mar 1 15:33:36 2018 +0400;Добавил распорядок смен для каждого подразделения. Смена назначается на месяц. Есть возможность сохранить любой вариант комбинирования 1,2 и 3 смены. Например (1,3); (2); ()... Добавлен интерфейс для просмотра и редактирования распорядка смен по каждому подразделению. Добавлена валидация - для каждого месяца может быть только один распорядок смен.
Aleksey Filippov;Thu Mar 1 15:23:06 2018 +0400;migrate to webjars
Aleksey Filippov;Thu Mar 1 13:31:16 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
Aleksey Filippov;Thu Mar 1 13:30:55 2018 +0400;create migrations for user models
Aleksey Filippov;Thu Mar 1 13:30:09 2018 +0400;old code refactoring
romanov73;Thu Mar 1 12:51:14 2018 +0400;add basic menu from rest
romanov73;Thu Mar 1 11:51:17 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
romanov73;Thu Mar 1 11:50:59 2018 +0400;try to fix ci: 3 remove tests
Aleksey Filippov;Thu Mar 1 11:50:25 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest
Aleksey Filippov;Thu Mar 1 11:49:59 2018 +0400;moved to new database
romanov73;Thu Mar 1 11:45:40 2018 +0400;try to fix ci: 2 change image
romanov73;Thu Mar 1 11:44:42 2018 +0400;try to fix ci: 1
romanov73;Thu Mar 1 11:15:52 2018 +0400;fix csrf tokens
Aleksey Filippov;Thu Mar 1 00:25:43 2018 +0400;initial user management support added
romanov73;Wed Feb 28 22:26:00 2018 +0400;save current version in local storage
romanov73;Wed Feb 28 18:40:57 2018 +0400;add ajax datatable
romanov73;Wed Feb 28 18:01:23 2018 +0400;add static index page with menu
Romanov Anton;Tue Feb 27 16:53:04 2018 +0000;Merge branch '33-' into 'develop'
romanov73;Tue Feb 27 20:50:44 2018 +0400;calc area balance
romanov73;Tue Feb 27 17:36:31 2018 +0400;remove unused panel
Romanov Anton;Tue Feb 27 13:06:02 2018 +0000;Merge branch '32-' into 'develop'
romanov73;Tue Feb 27 17:02:35 2018 +0400;calc tool balance
romanov73;Tue Feb 27 15:45:39 2018 +0400;calc tool balance
romanov73;Tue Feb 27 12:16:41 2018 +0400;calc tool power
romanov73;Tue Feb 27 11:40:59 2018 +0400;use tool types in dto
romanov73;Tue Feb 27 11:29:05 2018 +0400;fix dtos
romanov73;Mon Feb 26 23:50:53 2018 +0400;inherit dtos
romanov73;Mon Feb 26 15:38:00 2018 +0400;fix services for balance calculation
Aleksey Filippov;Mon Feb 26 15:00:48 2018 +0400;move from maven to gradle, move from javaee to spring boot
romanov73;Thu Feb 22 18:03:35 2018 +0400;Add menu resource
Romanov Anton;Tue Feb 20 18:12:55 2018 +0000;Merge branch 'balance-different-dto' into 'develop'
romanov73;Tue Feb 20 22:09:33 2018 +0400;restore tests
romanov73;Tue Feb 20 21:46:45 2018 +0400;filter employees by stage
romanov73;Tue Feb 20 21:21:27 2018 +0400;filter employees by workload
romanov73;Tue Feb 20 20:58:23 2018 +0400;Merge branch 'develop' into balance-different-dto
funny73;Tue Feb 20 19:52:24 2018 +0400;Add shift unit model Add link to unit view
romanov73;Tue Feb 20 16:14:09 2018 +0400;Important fix using coefficient
romanov73;Mon Feb 19 19:00:07 2018 +0400;partially fix ajustment
romanov73;Mon Feb 19 18:24:10 2018 +0400;Modify dto for using with different periods and work types
Romanov Anton;Mon Feb 19 08:11:39 2018 +0000;Merge branch '30-' into 'develop'
romanov73;Mon Feb 19 12:09:05 2018 +0400;Add work type to position
Romanov Anton;Wed Feb 14 20:57:35 2018 +0000;Merge branch '34-' into 'develop'
romanov73;Thu Feb 15 00:51:24 2018 +0400;fixes by comments
romanov73;Thu Feb 15 00:47:21 2018 +0400;merge fields
romanov73;Wed Feb 14 23:42:35 2018 +0400;Merge branch 'develop' into 34-gleb
romanov73;Wed Feb 14 21:05:51 2018 +0400;add clock
Romanov Anton;Wed Feb 14 16:49:29 2018 +0000;Merge branch '45-balance-employee-recomendations' into 'develop'
romanov73;Wed Feb 14 20:46:59 2018 +0400;cleanup code
romanov73;Wed Feb 14 18:25:42 2018 +0400;reduce code
romanov73;Wed Feb 14 18:24:59 2018 +0400;adjust additional employees
romanov73;Wed Feb 14 17:22:10 2018 +0400;move employees from other units
romanov73;Tue Feb 13 23:59:09 2018 +0400;fix save product name
romanov73;Tue Feb 13 23:28:30 2018 +0400;add converter, fix UI and backing for add complex object
romanov73;Tue Feb 13 22:10:59 2018 +0400;Merge branch 'develop' into 34-gleb
funny73;Tue Feb 13 18:07:25 2018 +0400;Add stage to workload
funny73;Tue Feb 13 16:05:45 2018 +0400;Add stage to category
romanov73;Tue Feb 13 13:20:16 2018 +0400;add map of all units balances
romanov73;Tue Feb 13 11:24:50 2018 +0400;move method
romanov73;Tue Feb 13 00:35:30 2018 +0400;add dialog
romanov73;Mon Feb 12 22:59:49 2018 +0400;filter only workshops and manufactures
romanov73;Mon Feb 12 22:04:55 2018 +0400;rename employees
romanov73;Mon Feb 12 17:34:37 2018 +0400;fix font size
romanov73;Mon Feb 12 16:59:16 2018 +0400;fix versions panel width
Romanov Anton;Mon Feb 12 12:47:04 2018 +0000;Merge branch '41-' into 'develop'
romanov73;Mon Feb 12 16:43:06 2018 +0400;change to working hours
romanov73;Mon Feb 12 14:05:36 2018 +0400;remove constructor
romanov73;Mon Feb 12 14:05:13 2018 +0400;Merge remote-tracking branch 'origin/develop' into develop
romanov73;Mon Feb 12 14:04:57 2018 +0400;fix selects
Romanov Anton;Mon Feb 12 09:20:05 2018 +0000;Merge branch '39-work-type-code' into 'develop'
Aleksey Filippov;Mon Feb 12 13:16:36 2018 +0400;add migration for work type code
Aleksey Filippov;Mon Feb 12 13:16:28 2018 +0400;add work type code support to xhtml
Aleksey Filippov;Mon Feb 12 13:16:12 2018 +0400;add work type code
Romanov Anton;Mon Feb 12 08:25:47 2018 +0000;Update README.md
Romanov Anton;Mon Feb 12 08:25:35 2018 +0000;Update README.md
Romanov Anton;Mon Feb 12 08:07:30 2018 +0000;Update README.md
Romanov Anton;Mon Feb 12 07:10:38 2018 +0000;Merge branch '40-' into 'develop'
Aleksey Filippov;Mon Feb 12 11:08:13 2018 +0400;add migration for position type
Aleksey Filippov;Mon Feb 12 11:07:45 2018 +0400;add position type support to xhtml
Aleksey Filippov;Mon Feb 12 11:07:23 2018 +0400;move position converter from boundary to view
Aleksey Filippov;Mon Feb 12 11:06:55 2018 +0400;add position type converter
Aleksey Filippov;Mon Feb 12 11:06:35 2018 +0400;add position type to backing
Aleksey Filippov;Mon Feb 12 11:06:00 2018 +0400;add position type support to service
Aleksey Filippov;Mon Feb 12 11:05:44 2018 +0400;add position type to model
Romanov Anton;Mon Feb 12 07:02:32 2018 +0000;Merge branch '33-' into 'develop'
romanov73;Mon Feb 12 14:58:51 2018 +0400;refactor
romanov73;Mon Feb 12 14:58:33 2018 +0400;add human hours coefficient
romanov73;Mon Feb 12 14:56:43 2018 +0400;fix api name
romanov73;Mon Feb 12 00:58:15 2018 +0400;add balance button
romanov73;Sun Feb 11 01:36:15 2018 +0400;add tools balance prototype
romanov73;Sat Feb 10 23:09:33 2018 +0400;move enum
romanov73;Sat Feb 10 23:02:36 2018 +0400;refactor
romanov73;Sat Feb 10 22:54:28 2018 +0400;add quarter
romanov73;Sat Feb 10 22:42:14 2018 +0400;refactor
romanov73;Sat Feb 10 20:52:46 2018 +0400;Merge branch 'develop' into 33-balance-area
romanov73;Sat Feb 10 20:52:32 2018 +0400;Merge branch 'develop' into 33-balance-area
Romanov Anton;Sat Feb 10 16:14:26 2018 +0000;Merge branch '31-map-2-dto' into 'develop'
romanov73;Sat Feb 10 19:35:17 2018 +0400;fixes after Almaz consultation
romanov73;Sat Feb 10 14:25:00 2018 +0400;fix npe
romanov73;Sat Feb 10 13:50:21 2018 +0400;add filter by work type
romanov73;Sat Feb 10 13:08:56 2018 +0400;rename field
romanov73;Sat Feb 10 13:08:38 2018 +0400;filter by all children
Aleksey Filippov;Sat Feb 10 11:26:17 2018 +0400;some fixes
Aleksey Filippov;Sat Feb 10 11:26:06 2018 +0400;add dto for total balance
Aleksey Filippov;Sat Feb 10 11:11:58 2018 +0400;add dto for employee load by unit
Aleksey Filippov;Sat Feb 10 10:30:11 2018 +0400;getEmployeesByUnit method refactoring
romanov73;Sat Feb 10 00:22:37 2018 +0400;add other type of balance area
romanov73;Fri Feb 9 22:03:39 2018 +0400;add tools balance
romanov73;Fri Feb 9 19:50:21 2018 +0400;divide balance page
Aleksey Filippov;Fri Feb 9 16:37:18 2018 +0400;adapt backing and view to areas and employee experience dtos
Aleksey Filippov;Fri Feb 9 16:36:18 2018 +0400;add dto for employee experience
Aleksey Filippov;Fri Feb 9 16:35:51 2018 +0400;add dto for areas
Aleksey Filippov;Fri Feb 9 14:57:01 2018 +0400;some ui fixes
Aleksey Filippov;Fri Feb 9 14:55:36 2018 +0400;move db methods from getters to init
romanov73;Thu Feb 8 01:20:12 2018 +0400;add areas panel
romanov73;Thu Feb 8 01:19:13 2018 +0400;add areas panel
romanov73;Thu Feb 8 01:02:39 2018 +0400;add global preloader
romanov73;Thu Feb 8 00:57:46 2018 +0400;select default unit
romanov73;Thu Feb 8 00:57:23 2018 +0400;add preloader
romanov73;Wed Feb 7 22:46:44 2018 +0400;change id
Romanov Anton;Wed Feb 7 13:53:35 2018 +0000;Merge branch '23-' into 'develop'
Romanov Anton;Wed Feb 7 13:49:27 2018 +0000;Merge branch 'deploy-fixes' into 'develop'
funny73;Wed Feb 7 17:34:41 2018 +0400;UDP modify AirplaneKitCounter to double
Aleksey Filippov;Wed Feb 7 17:33:23 2018 +0400;some wildfly deploy fixes
funny73;Wed Feb 7 17:19:22 2018 +0400;Modify AirplaneKitCounter to double -> Workload.java
funny73;Wed Feb 7 17:00:10 2018 +0400;Add AirplaneKitCounter to balance view -> BalanceEmployeeService.java Modify Worckload total Value (Value * AirplaneKitCounter) -> BalanceService.java
funny73;Wed Feb 7 14:13:35 2018 +0400;Add AirplaneKitCounter to view
funny73;Wed Feb 7 14:13:15 2018 +0400;Add AirplaneKitCounter to model Workload
funny73;Wed Feb 7 14:12:15 2018 +0400;Changelog for airplainetKitCounter
Romanov Anton;Wed Feb 7 06:54:55 2018 +0000;Merge branch '22-' into 'develop'
romanov73;Wed Feb 7 14:47:34 2018 +0400;add unit types
Romanov Anton;Tue Feb 6 22:43:40 2018 +0000;Merge branch '25-' into 'develop'
romanov73;Wed Feb 7 02:41:34 2018 +0400;filter balance by unit
romanov73;Wed Feb 7 02:06:46 2018 +0400;filter balance by unit
romanov73;Wed Feb 7 02:05:10 2018 +0400;filter balance by unit
romanov73;Wed Feb 7 00:41:01 2018 +0400;add method get all unit children
romanov73;Tue Feb 6 23:26:18 2018 +0400;add filter by unit in balance results
romanov73;Tue Feb 6 21:26:45 2018 +0400;refactor work with tree of menu items
romanov73;Tue Feb 6 21:08:01 2018 +0400;refactor work with tree
romanov73;Tue Feb 6 21:07:22 2018 +0400;fix update after remove enum key
romanov73;Tue Feb 6 20:03:31 2018 +0400;rename method
Gleb;Tue Feb 6 15:07:04 2018 +0000;Merge branch '17-' into 'develop'
romanov73;Tue Feb 6 21:15:12 2018 +0400;fix inf.
funny73;Tue Feb 6 19:02:36 2018 +0400;Add changelog for units with unit_type == 'Корпус', unit_type='Цех'
funny73;Tue Feb 6 18:19:50 2018 +0400;Remove "Korpus" from "Tip podrazdeleniya"
Romanov Anton;Tue Feb 6 11:31:43 2018 +0000;Merge branch '24-' into 'develop'
romanov73;Tue Feb 6 19:01:21 2018 +0400;add user
Romanov Anton;Thu Feb 1 19:45:21 2018 +0000;Merge branch 'wildfly-deploy' into 'develop'
Aleksey Filippov;Thu Feb 1 23:24:34 2018 +0400;add wildfly-maven-plugin, remove maven-glassfish-plugin
romanov73;Thu Feb 1 19:51:24 2018 +0400;add todos
Romanov Anton;Wed Jan 31 15:03:39 2018 +0000;Merge branch 'master' into 'develop'
romanov73;Wed Jan 31 18:53:27 2018 +0400;fix round
romanov73;Wed Jan 31 18:43:33 2018 +0400;fix round
romanov73;Wed Jan 31 18:38:33 2018 +0400;add balance by employees
romanov73;Wed Jan 31 18:30:18 2018 +0400;add balance by employees
Romanov Anton;Wed Jan 31 13:02:52 2018 +0000;Merge branch 'develop' into 'master'
Romanov Anton;Wed Jan 31 12:58:00 2018 +0000;Merge branch '4-' into 'develop'
romanov73;Wed Jan 31 16:47:51 2018 +0400;add balance by employees
romanov73;Wed Jan 31 00:47:39 2018 +0400;add calculation employee loads
romanov73;Wed Jan 31 00:47:25 2018 +0400;add calculation employee loads
romanov73;Wed Jan 31 00:47:08 2018 +0400;add calculation employee loads
romanov73;Wed Jan 31 00:46:53 2018 +0400;add calculation employee loads
romanov73;Wed Jan 31 00:45:59 2018 +0400;add interface for edit additional fields
romanov73;Wed Jan 31 00:45:04 2018 +0400;add comparable for using in tree map
romanov73;Wed Jan 31 00:44:33 2018 +0400;add fields for production program
romanov73;Wed Jan 31 00:43:56 2018 +0400;move interface to base entity
romanov73;Wed Jan 31 00:43:21 2018 +0400;add database fields for production program
romanov73;Wed Jan 31 00:42:55 2018 +0400;add dynamic columns
romanov73;Tue Jan 30 15:11:12 2018 +0400;add work type for workload
romanov73;Tue Jan 30 14:55:56 2018 +0400;add comments
romanov73;Tue Jan 30 03:00:28 2018 +0400;show by production program
romanov73;Tue Jan 30 00:07:31 2018 +0400;add employee available capacity
romanov73;Mon Jan 29 23:42:06 2018 +0400;fix units hierarchy bypass
romanov73;Mon Jan 29 23:28:28 2018 +0400;divide logic
romanov73;Mon Jan 29 22:59:45 2018 +0400;add employee experience table
romanov73;Mon Jan 29 22:15:29 2018 +0400;fix calc areas
romanov73;Mon Jan 29 21:57:23 2018 +0400;fix calc employee experience
romanov73;Mon Jan 29 21:12:52 2018 +0400;calc employee experience by units
romanov73;Mon Jan 29 14:01:36 2018 +0400;add employee experience
Romanov Anton;Mon Jan 29 06:04:01 2018 +0000;Update README.md
Romanov Anton;Sun Jan 28 16:16:56 2018 +0000;Merge branch 'develop' into 'master'
Romanov Anton;Sun Jan 28 16:06:14 2018 +0000;Merge branch '3-' into 'develop'
romanov73;Sun Jan 28 19:53:21 2018 +0400;remove product work types edit
romanov73;Sun Jan 28 19:41:31 2018 +0400;fix months select
romanov73;Sun Jan 28 19:06:10 2018 +0400;fix year select
romanov73;Sun Jan 28 04:28:21 2018 +0400;fix program edit
romanov73;Sun Jan 28 04:27:55 2018 +0400;add product backend
romanov73;Sun Jan 28 04:26:53 2018 +0400;add table and fields
Romanov Anton;Sat Jan 27 08:11:15 2018 +0000;Merge branch '9-' into 'develop'
romanov73;Sat Jan 27 15:28:22 2018 +0400;sort menu items, change logo
Romanov Anton;Sat Jan 27 05:55:52 2018 +0000;Update README.md
romanov73;Sat Jan 20 00:12:44 2018 +0400;fix versions select
Romanov Anton;Fri Jan 19 04:27:48 2018 +0000;Merge branch 'tool-square-catalog' into 'master'
Aleksey Filippov;Fri Jan 19 01:11:50 2018 +0400;add tool square catalog migration
Aleksey Filippov;Thu Jan 18 02:20:48 2018 +0400;add tool square catalog
romanov73;Thu Jan 11 21:22:14 2018 +0400;fix rest path
romanov73;Tue Jan 9 03:44:10 2018 +0400;fixes
romanov73;Tue Jan 9 00:09:27 2018 +0400;fix unit name
romanov73;Tue Jan 9 00:07:47 2018 +0400;fix year
romanov73;Mon Jan 8 19:16:28 2018 +0400;add short view
romanov73;Mon Jan 8 11:55:30 2018 +0400;fix
romanov73;Mon Jan 8 01:24:37 2018 +0400;fill work type by tool name
romanov73;Mon Jan 8 00:23:00 2018 +0400;ui fixes
romanov73;Sun Jan 7 23:54:40 2018 +0400;fix units hierarchy, add unit type, calc areas balance
romanov73;Sat Jan 6 22:51:11 2018 +0400;remove versions
romanov73;Sat Jan 6 22:06:03 2018 +0400;fix edit program
romanov73;Sat Jan 6 21:40:11 2018 +0400;add title image
romanov73;Sat Jan 6 18:04:27 2018 +0400;add production program input
romanov73;Wed Jan 3 21:05:48 2018 +0400;change wizard to tabs
romanov73;Wed Jan 3 01:03:05 2018 +0400;add wizard
romanov73;Sat Dec 30 02:02:42 2017 +0400;fix tools loading
romanov73;Sat Dec 30 01:46:01 2017 +0400;fix tools loading
romanov73;Sat Dec 30 01:32:33 2017 +0400;fix tools loading
romanov73;Sat Dec 30 01:28:24 2017 +0400;fix tools loading
romanov73;Sat Dec 30 00:54:26 2017 +0400;fix employee loading
romanov73;Fri Dec 29 19:04:14 2017 +0400;fix unit select
romanov73;Fri Dec 29 17:19:10 2017 +0400;fix unit select
romanov73;Fri Dec 29 01:40:38 2017 +0400;add cache
romanov73;Fri Dec 29 01:27:18 2017 +0400;add cache
romanov73;Fri Dec 29 01:14:09 2017 +0400;add cache
romanov73;Fri Dec 29 01:12:17 2017 +0400;change color
romanov73;Fri Dec 29 00:50:57 2017 +0400;add ci
romanov73;Fri Dec 29 00:48:10 2017 +0400;add ci
romanov73;Fri Dec 29 00:34:36 2017 +0400;add ci
romanov73;Fri Dec 29 00:31:22 2017 +0400;add ci
romanov73;Fri Dec 29 00:18:51 2017 +0400;add ci
romanov73;Fri Dec 29 00:16:44 2017 +0400;add ci
romanov73;Fri Dec 29 00:13:33 2017 +0400;add ci
romanov73;Fri Dec 29 00:09:51 2017 +0400;fix menus
Romanov Anton;Thu Dec 28 19:47:02 2017 +0000;Merge branch 'balance-example' into 'master'
romanov73;Thu Dec 28 23:44:52 2017 +0400;add other dictionaries
romanov73;Sat Dec 23 10:40:41 2017 +0400;add tool and work types
romanov73;Sat Dec 23 09:12:38 2017 +0400;fix menu item
romanov73;Sat Dec 23 09:12:23 2017 +0400;fix employee load
romanov73;Sat Dec 23 08:51:18 2017 +0400;add employee category
romanov73;Sat Dec 23 08:22:10 2017 +0400;add categories dictionary
romanov73;Fri Dec 22 12:38:08 2017 +0400;add menu item
romanov73;Fri Dec 22 12:31:26 2017 +0400;add employee category
romanov73;Fri Dec 22 09:55:31 2017 +0400;fix calendar
romanov73;Fri Dec 22 09:33:56 2017 +0400;fix date
romanov73;Thu Dec 21 16:38:20 2017 +0400;fix for context path change
romanov73;Thu Dec 21 13:58:02 2017 +0400;fluid panel
romanov73;Wed Dec 20 20:50:17 2017 +0400;fix context path
romanov73;Wed Dec 20 17:12:19 2017 +0400;fix displaying position
romanov73;Wed Dec 20 17:10:14 2017 +0400;load employees from file
romanov73;Wed Dec 20 15:09:54 2017 +0400;fix tool loading
romanov73;Mon Dec 18 18:18:04 2017 +0400;add resource versions
romanov73;Mon Dec 18 17:36:50 2017 +0400;add resource
romanov73;Mon Dec 18 17:07:47 2017 +0400;add swagger
romanov73;Sat Dec 16 09:42:46 2017 +0400;add new version
romanov73;Fri Dec 15 19:50:46 2017 +0400;fix title
romanov73;Fri Dec 15 19:24:13 2017 +0400;fix title
romanov73;Fri Dec 15 11:57:18 2017 +0400;add menu items
romanov73;Fri Dec 15 11:52:01 2017 +0400;add positions dictionary
romanov73;Fri Dec 15 11:29:02 2017 +0400;fix select for employee
romanov73;Fri Dec 15 10:35:12 2017 +0400;add employee backing
romanov73;Fri Dec 15 09:36:33 2017 +0400;fix 500 error page
romanov73;Thu Dec 14 22:26:45 2017 +0400;fix mapping
romanov73;Thu Dec 14 22:49:44 2017 +0400;add employee
romanov73;Thu Dec 14 22:37:30 2017 +0400;add employee
romanov73;Thu Dec 14 14:12:03 2017 +0400;fix crud service
romanov73;Thu Dec 14 12:51:59 2017 +0400;fix table style
romanov73;Thu Dec 14 12:22:48 2017 +0400;remove border for grid
romanov73;Thu Dec 14 12:20:14 2017 +0400;add version_id for units
romanov73;Thu Dec 14 11:49:53 2017 +0400;fix font size
romanov73;Thu Dec 14 11:08:46 2017 +0400;add unit fields
romanov73;Thu Dec 14 10:23:01 2017 +0400;KISS units hierarchy
romanov73;Wed Dec 13 19:04:05 2017 +0400;refresh page
romanov73;Wed Dec 13 17:43:54 2017 +0400;add version
romanov73;Wed Dec 13 01:46:05 2017 +0400;body width
romanov73;Wed Dec 13 01:45:17 2017 +0400;add menu item
romanov73;Tue Dec 12 22:20:07 2017 +0400;fix tree
romanov73;Tue Dec 12 20:02:20 2017 +0400;fix login
romanov73;Tue Dec 12 19:29:03 2017 +0400;add unit tree
romanov73;Tue Dec 12 18:23:38 2017 +0400;add unit dictionary
romanov73;Tue Dec 12 16:18:03 2017 +0400;add filter
romanov73;Tue Dec 12 14:13:31 2017 +0400;fix menu session
romanov73;Tue Dec 12 00:10:47 2017 +0400;remove bootstrap
romanov73;Mon Dec 11 21:53:18 2017 +0400;add unit select
romanov73;Mon Dec 11 19:49:15 2017 +0400;add menu item and role
romanov73;Mon Dec 11 19:37:38 2017 +0400;fix permissions
romanov73;Mon Dec 11 17:08:39 2017 +0400;refactor and add tools dictionary backing
romanov73;Mon Dec 11 17:08:04 2017 +0400;refactor and add tools dictionary backing
romanov73;Mon Dec 11 17:07:27 2017 +0400;refactor and add tools dictionary backing
romanov73;Mon Dec 11 14:12:24 2017 +0400;add tools dictionary page
romanov73;Sat Dec 9 09:54:45 2017 +0400;add global exception hadler
romanov73;Sat Nov 25 13:52:33 2017 +0400;add monitoring
romanov73;Sat Nov 25 13:21:18 2017 +0400;fix xls and xlsx
romanov73;Sat Nov 25 12:39:45 2017 +0400;upload tools
romanov73;Fri Nov 24 22:16:27 2017 +0400;save tools
romanov73;Fri Nov 24 13:06:21 2017 +0400;fix message
romanov73;Fri Nov 24 12:11:43 2017 +0400;add loading from xlsx
romanov73;Fri Nov 17 19:43:35 2017 +0400;add message
romanov73;Fri Nov 17 15:11:22 2017 +0400;fix button
romanov73;Fri Nov 17 10:15:42 2017 +0400;add registration service
romanov73;Thu Nov 16 21:35:04 2017 +0400;add push script
romanov73;Thu Nov 16 20:54:49 2017 +0400;fix named query execution
romanov73;Sun Nov 12 22:19:03 2017 +0400;sort menu items
romanov73;Sat Nov 11 15:09:35 2017 +0400;fix title
romanov73;Sat Nov 11 14:54:25 2017 +0400;fix table
romanov73;Sat Nov 11 14:35:38 2017 +0400;fix style attribute
romanov73;Sat Nov 11 14:08:56 2017 +0400;show user sessions
romanov73;Sat Nov 11 13:51:34 2017 +0400;rename service
romanov73;Fri Nov 10 22:03:25 2017 +0400;fix update tree
romanov73;Fri Nov 10 14:29:25 2017 +0400;edit menu items
romanov73;Fri Nov 10 12:17:24 2017 +0400;set bootstrap theme for primefaces
romanov73;Thu Nov 9 23:40:40 2017 +0400;fix saving entitites
romanov73;Thu Nov 9 23:27:42 2017 +0400;fix saving entitites
romanov73;Thu Nov 9 20:51:19 2017 +0400;fix titles
romanov73;Thu Nov 9 18:00:45 2017 +0400;fix login page title
romanov73;Thu Nov 9 13:36:21 2017 +0400;commit log
romanov73;Thu Nov 9 07:31:37 2017 +0400;reverse sort commits
romanov73;Thu Nov 9 00:27:48 2017 +0400;sort commits
romanov73;Wed Nov 8 23:16:07 2017 +0400;extend commits log
romanov73;Wed Nov 8 21:42:16 2017 +0400;fix styles
romanov73;Wed Nov 8 19:17:08 2017 +0400;add logout
romanov73;Wed Nov 8 19:13:17 2017 +0400;add logout
romanov73;Wed Nov 8 20:18:35 2017 +0400;Merge branch 'master' of gitlab.com:romanov73/balance
romanov73;Wed Nov 8 20:17:55 2017 +0400;add database diagramm
Romanov Anton;Wed Nov 8 04:13:39 2017 +0000;Update README.md
Romanov Anton;Wed Nov 8 04:11:33 2017 +0000;Update README.md
romanov73;Wed Nov 8 07:48:13 2017 +0400;Merge remote-tracking branch 'origin/master'
Romanov Anton;Tue Nov 7 18:05:04 2017 +0000;Update README.md
romanov73;Tue Nov 7 22:01:50 2017 +0400;add example of permissions validation
romanov73;Tue Nov 7 20:56:31 2017 +0400;rename project
romanov73;Wed Oct 11 21:37:49 2017 +0400;reverse sort commits
romanov73;Wed Oct 11 21:32:19 2017 +0400;fix read resource as file
romanov73;Wed Oct 11 20:23:10 2017 +0400;Merge remote-tracking branch 'origin/master'
romanov73;Wed Oct 11 20:22:52 2017 +0400;add commits log
Romanov Anton;Tue Oct 10 20:47:29 2017 +0000;Update README.md
romanov73;Fri Oct 6 01:55:57 2017 +0400;add menu to platform

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

Loading…
Cancel
Save