diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..13e3454
--- /dev/null
+++ b/build.gradle
@@ -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'
+}
\ No newline at end of file
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..5877ec9
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..59c3905
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..98bcec3
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -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
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..9aa616c
--- /dev/null
+++ b/gradlew
@@ -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 "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -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
diff --git a/papers.html b/papers.html
deleted file mode 100644
index 6a67c4e..0000000
--- a/papers.html
+++ /dev/null
@@ -1,112 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- NG-Tacker
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/java/ru/ulstu/NgTrackerApplication.java b/src/main/java/ru/ulstu/NgTrackerApplication.java
new file mode 100644
index 0000000..5e6ee4f
--- /dev/null
+++ b/src/main/java/ru/ulstu/NgTrackerApplication.java
@@ -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);
+ }
+}
diff --git a/src/main/java/ru/ulstu/commit/controller/CommitController.java b/src/main/java/ru/ulstu/commit/controller/CommitController.java
new file mode 100644
index 0000000..44f99cb
--- /dev/null
+++ b/src/main/java/ru/ulstu/commit/controller/CommitController.java
@@ -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 {
+ 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> getCommits(@RequestParam(value = "offset", defaultValue = "0") int offset,
+ @RequestParam(value = "count", defaultValue = "10") int count) {
+ return new Response<>(commitService.getCommits(offset, count));
+ }
+}
diff --git a/src/main/java/ru/ulstu/commit/model/CommitListDto.java b/src/main/java/ru/ulstu/commit/model/CommitListDto.java
new file mode 100644
index 0000000..4650484
--- /dev/null
+++ b/src/main/java/ru/ulstu/commit/model/CommitListDto.java
@@ -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 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;
+ }
+}
diff --git a/src/main/java/ru/ulstu/commit/service/CommitService.java b/src/main/java/ru/ulstu/commit/service/CommitService.java
new file mode 100644
index 0000000..f77d3ac
--- /dev/null
+++ b/src/main/java/ru/ulstu/commit/service/CommitService.java
@@ -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 commits;
+
+ public CommitService() {
+ commits = getCommits();
+ }
+
+ public PageableItems getCommits(int offset, int count) {
+ return new PageableItems<>(commits.size(), commits.stream()
+ .skip(offset)
+ .limit(count)
+ .collect(Collectors.toList()));
+ }
+
+ private List getCommits() {
+ return getFileContent()
+ .stream()
+ .map(CommitListDto::new)
+ .filter(Objects::nonNull)
+ .sorted((c1, c2) -> c2.getDate().compareTo(c1.getDate()))
+ .collect(Collectors.toList());
+ }
+
+ private List getFileContent() {
+
+ List 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;
+ }
+}
+
diff --git a/src/main/java/ru/ulstu/configuration/ApplicationProperties.java b/src/main/java/ru/ulstu/configuration/ApplicationProperties.java
new file mode 100644
index 0000000..b89641a
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/ApplicationProperties.java
@@ -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;
+ }
+}
diff --git a/src/main/java/ru/ulstu/configuration/AsyncConfiguration.java b/src/main/java/ru/ulstu/configuration/AsyncConfiguration.java
new file mode 100644
index 0000000..bdf0613
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/AsyncConfiguration.java
@@ -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();
+ }
+}
diff --git a/src/main/java/ru/ulstu/configuration/Constants.java b/src/main/java/ru/ulstu/configuration/Constants.java
new file mode 100644
index 0000000..0a2268a
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/Constants.java
@@ -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";
+
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/configuration/ControllersConfiguration.java b/src/main/java/ru/ulstu/configuration/ControllersConfiguration.java
new file mode 100644
index 0000000..2d92313
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/ControllersConfiguration.java
@@ -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);
+ }
+}
diff --git a/src/main/java/ru/ulstu/configuration/HttpListenerConfiguration.java b/src/main/java/ru/ulstu/configuration/HttpListenerConfiguration.java
new file mode 100644
index 0000000..bcbcab8
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/HttpListenerConfiguration.java
@@ -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);
+ }
+ }
+}
diff --git a/src/main/java/ru/ulstu/configuration/JacksonConfiguration.java b/src/main/java/ru/ulstu/configuration/JacksonConfiguration.java
new file mode 100644
index 0000000..108a28f
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/JacksonConfiguration.java
@@ -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();
+ }
+}
diff --git a/src/main/java/ru/ulstu/configuration/MailTemplateConfiguration.java b/src/main/java/ru/ulstu/configuration/MailTemplateConfiguration.java
new file mode 100644
index 0000000..68c201b
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/MailTemplateConfiguration.java
@@ -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;
+ }
+}
diff --git a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java
new file mode 100644
index 0000000..bbe0885
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java
@@ -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/");
+ }
+}
diff --git a/src/main/java/ru/ulstu/configuration/PasswordEncoderConfiguration.java b/src/main/java/ru/ulstu/configuration/PasswordEncoderConfiguration.java
new file mode 100644
index 0000000..19370da
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/PasswordEncoderConfiguration.java
@@ -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();
+ }
+}
diff --git a/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java
new file mode 100644
index 0000000..c0f34d6
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/SecurityConfiguration.java
@@ -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);
+ }
+ }
+}
diff --git a/src/main/java/ru/ulstu/configuration/SwaggerConfiguration.java b/src/main/java/ru/ulstu/configuration/SwaggerConfiguration.java
new file mode 100644
index 0000000..c51811e
--- /dev/null
+++ b/src/main/java/ru/ulstu/configuration/SwaggerConfiguration.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/core/controller/AdviceController.java b/src/main/java/ru/ulstu/core/controller/AdviceController.java
new file mode 100644
index 0000000..916359a
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/controller/AdviceController.java
@@ -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 handleException(ErrorConstants error) {
+ log.warn(error.toString());
+ return new Response<>(error);
+ }
+
+ private ResponseExtended handleException(ErrorConstants error, E errorData) {
+ log.warn(error.toString());
+ return new ResponseExtended<>(error, errorData);
+ }
+
+ @ExceptionHandler(EntityIdIsNullException.class)
+ public Response handleEntityIdIsNullException(Throwable e) {
+ return handleException(ErrorConstants.ID_IS_NULL);
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseExtended> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+ final Set 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 handleUserIdExistsException(Throwable e) {
+ return handleException(ErrorConstants.USER_ID_EXISTS);
+ }
+
+ @ExceptionHandler(UserActivationError.class)
+ public ResponseExtended handleUserActivationError(Throwable e) {
+ return handleException(ErrorConstants.USER_ACTIVATION_ERROR, e.getMessage());
+ }
+
+ @ExceptionHandler(UserLoginExistsException.class)
+ public ResponseExtended handleUserLoginExistsException(Throwable e) {
+ return handleException(ErrorConstants.USER_LOGIN_EXISTS, e.getMessage());
+ }
+
+ @ExceptionHandler(UserEmailExistsException.class)
+ public ResponseExtended handleUserEmailExistsException(Throwable e) {
+ return handleException(ErrorConstants.USER_EMAIL_EXISTS, e.getMessage());
+ }
+
+ @ExceptionHandler(UserPasswordsNotValidOrNotMatchException.class)
+ public Response handleUserPasswordsNotValidOrNotMatchException(Throwable e) {
+ return handleException(ErrorConstants.USER_PASSWORDS_NOT_VALID_OR_NOT_MATCH);
+ }
+
+ @ExceptionHandler(UserNotFoundException.class)
+ public ResponseExtended handleUserNotFoundException(Throwable e) {
+ return handleException(ErrorConstants.USER_NOT_FOUND, e.getMessage());
+ }
+
+ @ExceptionHandler(UserNotActivatedException.class)
+ public Response handleUserNotActivatedException(Throwable e) {
+ return handleException(ErrorConstants.USER_NOT_ACTIVATED);
+ }
+
+ @ExceptionHandler(UserResetKeyError.class)
+ public ResponseExtended handleUserResetKeyError(Throwable e) {
+ return handleException(ErrorConstants.USER_RESET_ERROR, e.getMessage());
+ }
+
+ @ExceptionHandler(UserIsUndeadException.class)
+ public ResponseExtended handleUserIsUndeadException(Throwable e) {
+ return handleException(ErrorConstants.USER_UNDEAD_ERROR, e.getMessage());
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseExtended handleUnknownException(Throwable e) {
+ e.printStackTrace();
+ return handleException(ErrorConstants.UNKNOWN, e.getMessage());
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/error/EntityIdIsNullException.java b/src/main/java/ru/ulstu/core/error/EntityIdIsNullException.java
new file mode 100644
index 0000000..1c95a28
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/error/EntityIdIsNullException.java
@@ -0,0 +1,6 @@
+package ru.ulstu.core.error;
+
+public class EntityIdIsNullException extends RuntimeException {
+ public EntityIdIsNullException() {
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/error/OdinException.java b/src/main/java/ru/ulstu/core/error/OdinException.java
new file mode 100644
index 0000000..a7513fa
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/error/OdinException.java
@@ -0,0 +1,7 @@
+package ru.ulstu.core.error;
+
+public class OdinException extends RuntimeException {
+ public OdinException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/error/XlsLoadException.java b/src/main/java/ru/ulstu/core/error/XlsLoadException.java
new file mode 100644
index 0000000..372731f
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/error/XlsLoadException.java
@@ -0,0 +1,7 @@
+package ru.ulstu.core.error;
+
+public class XlsLoadException extends Exception {
+ public XlsLoadException(String s) {
+ super(s);
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/error/XlsParseException.java b/src/main/java/ru/ulstu/core/error/XlsParseException.java
new file mode 100644
index 0000000..fe8c4b3
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/error/XlsParseException.java
@@ -0,0 +1,7 @@
+package ru.ulstu.core.error;
+
+public class XlsParseException extends Exception {
+ public XlsParseException(String s) {
+ super(s);
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/jpa/OffsetablePageRequest.java b/src/main/java/ru/ulstu/core/jpa/OffsetablePageRequest.java
new file mode 100644
index 0000000..388e9a1
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/jpa/OffsetablePageRequest.java
@@ -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;
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/model/BaseEntity.java b/src/main/java/ru/ulstu/core/model/BaseEntity.java
new file mode 100644
index 0000000..bd0f1e4
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/model/BaseEntity.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/ulstu/core/model/ErrorConstants.java b/src/main/java/ru/ulstu/core/model/ErrorConstants.java
new file mode 100644
index 0000000..ad69b86
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/model/ErrorConstants.java
@@ -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);
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/model/TreeDto.java b/src/main/java/ru/ulstu/core/model/TreeDto.java
new file mode 100644
index 0000000..491553c
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/model/TreeDto.java
@@ -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 children = new ArrayList<>();
+
+ public TreeDto() {
+ }
+
+ public 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 getChildren() {
+ return children;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/model/TreeEntity.java b/src/main/java/ru/ulstu/core/model/TreeEntity.java
new file mode 100644
index 0000000..48f1202
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/model/TreeEntity.java
@@ -0,0 +1,16 @@
+package ru.ulstu.core.model;
+
+import java.util.List;
+
+public interface TreeEntity {
+
+ Integer getId();
+
+ List getChildren();
+
+ void setChildren(List children);
+
+ T getParent();
+
+ void setParent(T parent);
+}
diff --git a/src/main/java/ru/ulstu/core/model/response/ControllerResponse.java b/src/main/java/ru/ulstu/core/model/response/ControllerResponse.java
new file mode 100644
index 0000000..72c5ebf
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/model/response/ControllerResponse.java
@@ -0,0 +1,24 @@
+package ru.ulstu.core.model.response;
+
+class ControllerResponse {
+ private D data;
+ private ControllerResponseError error;
+
+ ControllerResponse(D data) {
+ this.data = data;
+ this.error = null;
+ }
+
+ ControllerResponse(ControllerResponseError error) {
+ this.data = null;
+ this.error = error;
+ }
+
+ public D getData() {
+ return data;
+ }
+
+ public ControllerResponseError getError() {
+ return error;
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/model/response/ControllerResponseError.java b/src/main/java/ru/ulstu/core/model/response/ControllerResponseError.java
new file mode 100644
index 0000000..a26b088
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/model/response/ControllerResponseError.java
@@ -0,0 +1,25 @@
+package ru.ulstu.core.model.response;
+
+import ru.ulstu.core.model.ErrorConstants;
+
+class ControllerResponseError {
+ 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;
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/model/response/PageableItems.java b/src/main/java/ru/ulstu/core/model/response/PageableItems.java
new file mode 100644
index 0000000..ab3047f
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/model/response/PageableItems.java
@@ -0,0 +1,21 @@
+package ru.ulstu.core.model.response;
+
+import java.util.Collection;
+
+public class PageableItems {
+ private final long count;
+ private final Collection items;
+
+ public PageableItems(long count, Collection items) {
+ this.count = count;
+ this.items = items;
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public Collection getItems() {
+ return items;
+ }
+}
diff --git a/src/main/java/ru/ulstu/core/model/response/Response.java b/src/main/java/ru/ulstu/core/model/response/Response.java
new file mode 100644
index 0000000..4722010
--- /dev/null
+++ b/src/main/java/ru/ulstu/core/model/response/Response.java
@@ -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 extends ResponseEntity