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 { + + public Response(D data) { + super(new ControllerResponse(data), HttpStatus.OK); + } + + public Response(ErrorConstants error) { + super(new ControllerResponse(new ControllerResponseError<>(error, null)), HttpStatus.OK); + } +} diff --git a/src/main/java/ru/ulstu/core/model/response/ResponseExtended.java b/src/main/java/ru/ulstu/core/model/response/ResponseExtended.java new file mode 100644 index 0000000..1829622 --- /dev/null +++ b/src/main/java/ru/ulstu/core/model/response/ResponseExtended.java @@ -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 extends ResponseEntity { + + public ResponseExtended(ErrorConstants error, E errorData) { + super(new ControllerResponse(new ControllerResponseError(error, errorData)), HttpStatus.OK); + } +} diff --git a/src/main/java/ru/ulstu/core/repository/JpaDetachableRepository.java b/src/main/java/ru/ulstu/core/repository/JpaDetachableRepository.java new file mode 100644 index 0000000..2bf1c04 --- /dev/null +++ b/src/main/java/ru/ulstu/core/repository/JpaDetachableRepository.java @@ -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 extends JpaRepository { + void detach(T t); +} diff --git a/src/main/java/ru/ulstu/core/repository/JpaDetachableRepositoryImpl.java b/src/main/java/ru/ulstu/core/repository/JpaDetachableRepositoryImpl.java new file mode 100644 index 0000000..097a2a5 --- /dev/null +++ b/src/main/java/ru/ulstu/core/repository/JpaDetachableRepositoryImpl.java @@ -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 extends SimpleJpaRepository + implements JpaDetachableRepository { + private EntityManager entityManager; + + public JpaDetachableRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { + super(entityInformation, entityManager); + this.entityManager = entityManager; + } + + @Override + public void detach(T t) { + entityManager.detach(t); + } +} diff --git a/src/main/java/ru/ulstu/core/service/TreeService.java b/src/main/java/ru/ulstu/core/service/TreeService.java new file mode 100644 index 0000000..1e3d4a0 --- /dev/null +++ b/src/main/java/ru/ulstu/core/service/TreeService.java @@ -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 { + public TreeDto getTree(String rootName, List rootItems) { + return addChildNode(new TreeDto(rootName), rootItems, element -> true); + } + + public TreeDto getTree(String rootName, List rootItems, Predicate filterPredicate) { + return addChildNode(new TreeDto(rootName), rootItems, filterPredicate); + } + + private TreeDto addChildNode(TreeDto currentRoot, List children, Predicate 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; + } +} diff --git a/src/main/java/ru/ulstu/core/service/XlsDocumentBuilder.java b/src/main/java/ru/ulstu/core/service/XlsDocumentBuilder.java new file mode 100644 index 0000000..d70d86e --- /dev/null +++ b/src/main/java/ru/ulstu/core/service/XlsDocumentBuilder.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/core/util/DateUtils.java b/src/main/java/ru/ulstu/core/util/DateUtils.java new file mode 100644 index 0000000..a8853b4 --- /dev/null +++ b/src/main/java/ru/ulstu/core/util/DateUtils.java @@ -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 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()); + } +} diff --git a/src/main/java/ru/ulstu/core/util/NumberUtils.java b/src/main/java/ru/ulstu/core/util/NumberUtils.java new file mode 100644 index 0000000..5e002cd --- /dev/null +++ b/src/main/java/ru/ulstu/core/util/NumberUtils.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/core/util/StreamApiUtils.java b/src/main/java/ru/ulstu/core/util/StreamApiUtils.java new file mode 100644 index 0000000..282601c --- /dev/null +++ b/src/main/java/ru/ulstu/core/util/StreamApiUtils.java @@ -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 List convert(List entitites, Function converter) { + return entitites.stream().map(e -> converter.apply(e)).collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/ulstu/odin/controller/OdinController.java b/src/main/java/ru/ulstu/odin/controller/OdinController.java new file mode 100644 index 0000000..8d4fcf4 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/controller/OdinController.java @@ -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 { + public static final String META_LIST_URL = "/meta/list"; + public static final String META_ELEMENT_URL = "/meta/element"; + + private Class listDtoClass; + private Class elementDtoClass; + @Autowired + private OdinService odinService; + + public OdinController(Class listDtoClass) { + this(listDtoClass, null); + } + + public OdinController(Class listDtoClass, Class elementDtoClass) { + this.listDtoClass = listDtoClass; + this.elementDtoClass = elementDtoClass; + } + + @GetMapping(META_LIST_URL) + public Response getListModel() { + return new Response<>(odinService.getListModel(listDtoClass)); + } + + @GetMapping(META_ELEMENT_URL) + public Response getElementModel() { + return new Response<>(odinService.getElementModel(elementDtoClass)); + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/odin/model/OdinBooleanField.java b/src/main/java/ru/ulstu/odin/model/OdinBooleanField.java new file mode 100644 index 0000000..02ab547 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinBooleanField.java @@ -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); + } +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinCollectionField.java b/src/main/java/ru/ulstu/odin/model/OdinCollectionField.java new file mode 100644 index 0000000..171e78c --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinCollectionField.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinDateField.java b/src/main/java/ru/ulstu/odin/model/OdinDateField.java new file mode 100644 index 0000000..9d7b037 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinDateField.java @@ -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(); + } +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinDto.java b/src/main/java/ru/ulstu/odin/model/OdinDto.java new file mode 100644 index 0000000..12d4286 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinDto.java @@ -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(); +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinField.java b/src/main/java/ru/ulstu/odin/model/OdinField.java new file mode 100644 index 0000000..5917a44 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinField.java @@ -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 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 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 cast(Object value, Class clazz) { + try { + return clazz.cast(value); + } catch (ClassCastException e) { + return null; + } + } + + protected T getValue(Class annotationClass, String valueName, Class 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); + } +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinMetadata.java b/src/main/java/ru/ulstu/odin/model/OdinMetadata.java new file mode 100644 index 0000000..fff333a --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinMetadata.java @@ -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 fields; + + public OdinMetadata(boolean odinDto, List 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 getFields() { + return fields; + } +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinNumericField.java b/src/main/java/ru/ulstu/odin/model/OdinNumericField.java new file mode 100644 index 0000000..0739745 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinNumericField.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinObjectField.java b/src/main/java/ru/ulstu/odin/model/OdinObjectField.java new file mode 100644 index 0000000..d19e748 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinObjectField.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinStringField.java b/src/main/java/ru/ulstu/odin/model/OdinStringField.java new file mode 100644 index 0000000..5157d7e --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinStringField.java @@ -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(); + } +} diff --git a/src/main/java/ru/ulstu/odin/model/OdinVoid.java b/src/main/java/ru/ulstu/odin/model/OdinVoid.java new file mode 100644 index 0000000..b643dae --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/OdinVoid.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/odin/model/annotation/OdinCaption.java b/src/main/java/ru/ulstu/odin/model/annotation/OdinCaption.java new file mode 100644 index 0000000..a68d7d4 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/annotation/OdinCaption.java @@ -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(); +} diff --git a/src/main/java/ru/ulstu/odin/model/annotation/OdinDate.java b/src/main/java/ru/ulstu/odin/model/annotation/OdinDate.java new file mode 100644 index 0000000..9a67841 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/annotation/OdinDate.java @@ -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; +} diff --git a/src/main/java/ru/ulstu/odin/model/annotation/OdinNumeric.java b/src/main/java/ru/ulstu/odin/model/annotation/OdinNumeric.java new file mode 100644 index 0000000..e9eb722 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/annotation/OdinNumeric.java @@ -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; +} diff --git a/src/main/java/ru/ulstu/odin/model/annotation/OdinReadOnly.java b/src/main/java/ru/ulstu/odin/model/annotation/OdinReadOnly.java new file mode 100644 index 0000000..e18732b --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/annotation/OdinReadOnly.java @@ -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 { +} diff --git a/src/main/java/ru/ulstu/odin/model/annotation/OdinString.java b/src/main/java/ru/ulstu/odin/model/annotation/OdinString.java new file mode 100644 index 0000000..61360e1 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/annotation/OdinString.java @@ -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; +} diff --git a/src/main/java/ru/ulstu/odin/model/annotation/OdinVisible.java b/src/main/java/ru/ulstu/odin/model/annotation/OdinVisible.java new file mode 100644 index 0000000..14f82d7 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/model/annotation/OdinVisible.java @@ -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; +} diff --git a/src/main/java/ru/ulstu/odin/service/OdinService.java b/src/main/java/ru/ulstu/odin/service/OdinService.java new file mode 100644 index 0000000..9de2426 --- /dev/null +++ b/src/main/java/ru/ulstu/odin/service/OdinService.java @@ -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 { + private final Logger log = LoggerFactory.getLogger(OdinService.class); + + private final Map 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 List getDtoMetaModel(Class 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 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 elementDtoClass) { + return elementDtoClass == null + ? null : + new OdinMetadata(true, getDtoMetaModel(elementDtoClass)); + } +} diff --git a/src/main/java/ru/ulstu/odinexample/controller/OdinExampleController.java b/src/main/java/ru/ulstu/odinexample/controller/OdinExampleController.java new file mode 100644 index 0000000..0101531 --- /dev/null +++ b/src/main/java/ru/ulstu/odinexample/controller/OdinExampleController.java @@ -0,0 +1,4 @@ +package ru.ulstu.odinexample.controller; + +public class OdinExampleController { +} diff --git a/src/main/java/ru/ulstu/odinexample/model/OdinExampleDto.java b/src/main/java/ru/ulstu/odinexample/model/OdinExampleDto.java new file mode 100644 index 0000000..027d96f --- /dev/null +++ b/src/main/java/ru/ulstu/odinexample/model/OdinExampleDto.java @@ -0,0 +1,4 @@ +package ru.ulstu.odinexample.model; + +public class OdinExampleDto { +} diff --git a/src/main/java/ru/ulstu/odinexample/model/OdinExampleListDto.java b/src/main/java/ru/ulstu/odinexample/model/OdinExampleListDto.java new file mode 100644 index 0000000..6924519 --- /dev/null +++ b/src/main/java/ru/ulstu/odinexample/model/OdinExampleListDto.java @@ -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; + } + +} diff --git a/src/main/java/ru/ulstu/odinexample/service/OdinExampleService.java b/src/main/java/ru/ulstu/odinexample/service/OdinExampleService.java new file mode 100644 index 0000000..57cea21 --- /dev/null +++ b/src/main/java/ru/ulstu/odinexample/service/OdinExampleService.java @@ -0,0 +1,4 @@ +package ru.ulstu.odinexample.service; + +public class OdinExampleService { +} diff --git a/src/main/java/ru/ulstu/user/component/IpAddressResolver.java b/src/main/java/ru/ulstu/user/component/IpAddressResolver.java new file mode 100644 index 0000000..4c74923 --- /dev/null +++ b/src/main/java/ru/ulstu/user/component/IpAddressResolver.java @@ -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(); + } + +} diff --git a/src/main/java/ru/ulstu/user/component/UserSessionLoginHandler.java b/src/main/java/ru/ulstu/user/component/UserSessionLoginHandler.java new file mode 100644 index 0000000..75187dc --- /dev/null +++ b/src/main/java/ru/ulstu/user/component/UserSessionLoginHandler.java @@ -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); + } + } +} diff --git a/src/main/java/ru/ulstu/user/component/UserSessionLogoutHandler.java b/src/main/java/ru/ulstu/user/component/UserSessionLogoutHandler.java new file mode 100644 index 0000000..606cbcd --- /dev/null +++ b/src/main/java/ru/ulstu/user/component/UserSessionLogoutHandler.java @@ -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); + } +} diff --git a/src/main/java/ru/ulstu/user/controller/UserController.java b/src/main/java/ru/ulstu/user/controller/UserController.java new file mode 100644 index 0000000..f9f49e3 --- /dev/null +++ b/src/main/java/ru/ulstu/user/controller/UserController.java @@ -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 { + 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 odinRolesService; + private final OdinService odinSessionsService; + + public UserController(UserService userService, + UserSessionService userSessionService, + OdinService odinRolesService, + OdinService 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> getUserRoles() { + log.debug("REST: UserController.getUserRoles()"); + return new Response<>(userService.getUserRoles()); + } + + @GetMapping(ROLES_META_URL) + @Secured(UserRoleConstants.ADMIN) + public Response getUserRolesMetaData() { + log.debug("REST: UserController.getUserRolesMetaData()"); + return new Response<>(odinRolesService.getListModel(UserRoleDto.class)); + } + + @GetMapping(SESSIONS_URL) + @Secured(UserRoleConstants.ADMIN) + public Response> 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 getUserSessionsMetaData() { + log.debug("REST: UserController.getUserSessionsMetaData()"); + return new Response<>(odinSessionsService.getListModel(UserSessionListDto.class)); + } + + @GetMapping("") + @Secured(UserRoleConstants.ADMIN) + public Response> 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 getUser(@PathVariable Integer userId) { + log.debug("REST: UserController.getUser( {} )", userId); + return new Response<>(userService.getUserWithRolesById(userId)); + } + + + @PostMapping("") + @Secured(UserRoleConstants.ADMIN) + public Response createUser(@Valid @RequestBody UserDto userDto) { + log.debug("REST: UserController.createUser( {} )", userDto.getLogin()); + return new Response<>(userService.createUser(userDto)); + } + + @PutMapping("") + @Secured(UserRoleConstants.ADMIN) + public Response 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 deleteUser(@PathVariable Integer userId) { + log.debug("REST: UserController.deleteUser( {} )", userId); + return new Response<>(userService.deleteUser(userId)); + } + + @PostMapping(REGISTER_URL) + public Response registerUser(@Valid @RequestBody UserDto userDto) { + log.debug("REST: UserController.registerUser( {} )", userDto.getLogin()); + return new Response<>(userService.createUser(userDto)); + } + + @PostMapping(ACTIVATE_URL) + public Response 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 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 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 requestPasswordReset(@RequestParam("email") String email) { + log.debug("REST: UserController.requestPasswordReset( {} )", email); + return new Response<>(userService.requestUserPasswordReset(email)); + } + + @PostMapping(PASSWORD_RESET_URL) + public Response finishPasswordReset(@RequestParam("key") String key, + @RequestBody UserResetPasswordDto userResetPasswordDto) { + log.debug("REST: UserController.requestPasswordReset( {} )", key); + return new Response<>(userService.completeUserPasswordReset(key, userResetPasswordDto)); + } +} diff --git a/src/main/java/ru/ulstu/user/error/UserActivationError.java b/src/main/java/ru/ulstu/user/error/UserActivationError.java new file mode 100644 index 0000000..31cd2c4 --- /dev/null +++ b/src/main/java/ru/ulstu/user/error/UserActivationError.java @@ -0,0 +1,7 @@ +package ru.ulstu.user.error; + +public class UserActivationError extends RuntimeException { + public UserActivationError(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ulstu/user/error/UserEmailExistsException.java b/src/main/java/ru/ulstu/user/error/UserEmailExistsException.java new file mode 100644 index 0000000..2864dc5 --- /dev/null +++ b/src/main/java/ru/ulstu/user/error/UserEmailExistsException.java @@ -0,0 +1,7 @@ +package ru.ulstu.user.error; + +public class UserEmailExistsException extends RuntimeException { + public UserEmailExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ulstu/user/error/UserIdExistsException.java b/src/main/java/ru/ulstu/user/error/UserIdExistsException.java new file mode 100644 index 0000000..f6a15b5 --- /dev/null +++ b/src/main/java/ru/ulstu/user/error/UserIdExistsException.java @@ -0,0 +1,6 @@ +package ru.ulstu.user.error; + +public class UserIdExistsException extends RuntimeException { + public UserIdExistsException() { + } +} diff --git a/src/main/java/ru/ulstu/user/error/UserIsUndeadException.java b/src/main/java/ru/ulstu/user/error/UserIsUndeadException.java new file mode 100644 index 0000000..1c727e0 --- /dev/null +++ b/src/main/java/ru/ulstu/user/error/UserIsUndeadException.java @@ -0,0 +1,7 @@ +package ru.ulstu.user.error; + +public class UserIsUndeadException extends RuntimeException { + public UserIsUndeadException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ulstu/user/error/UserLoginExistsException.java b/src/main/java/ru/ulstu/user/error/UserLoginExistsException.java new file mode 100644 index 0000000..c6edbbf --- /dev/null +++ b/src/main/java/ru/ulstu/user/error/UserLoginExistsException.java @@ -0,0 +1,7 @@ +package ru.ulstu.user.error; + +public class UserLoginExistsException extends RuntimeException { + public UserLoginExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ulstu/user/error/UserNotActivatedException.java b/src/main/java/ru/ulstu/user/error/UserNotActivatedException.java new file mode 100644 index 0000000..4931898 --- /dev/null +++ b/src/main/java/ru/ulstu/user/error/UserNotActivatedException.java @@ -0,0 +1,6 @@ +package ru.ulstu.user.error; + +public class UserNotActivatedException extends RuntimeException { + public UserNotActivatedException() { + } +} diff --git a/src/main/java/ru/ulstu/user/error/UserNotFoundException.java b/src/main/java/ru/ulstu/user/error/UserNotFoundException.java new file mode 100644 index 0000000..a3b6fdf --- /dev/null +++ b/src/main/java/ru/ulstu/user/error/UserNotFoundException.java @@ -0,0 +1,7 @@ +package ru.ulstu.user.error; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ulstu/user/error/UserPasswordsNotValidOrNotMatchException.java b/src/main/java/ru/ulstu/user/error/UserPasswordsNotValidOrNotMatchException.java new file mode 100644 index 0000000..088f999 --- /dev/null +++ b/src/main/java/ru/ulstu/user/error/UserPasswordsNotValidOrNotMatchException.java @@ -0,0 +1,6 @@ +package ru.ulstu.user.error; + +public class UserPasswordsNotValidOrNotMatchException extends RuntimeException { + public UserPasswordsNotValidOrNotMatchException() { + } +} diff --git a/src/main/java/ru/ulstu/user/error/UserResetKeyError.java b/src/main/java/ru/ulstu/user/error/UserResetKeyError.java new file mode 100644 index 0000000..73ab6e1 --- /dev/null +++ b/src/main/java/ru/ulstu/user/error/UserResetKeyError.java @@ -0,0 +1,7 @@ +package ru.ulstu.user.error; + +public class UserResetKeyError extends RuntimeException { + public UserResetKeyError(String message) { + super(message); + } +} diff --git a/src/main/java/ru/ulstu/user/model/User.java b/src/main/java/ru/ulstu/user/model/User.java new file mode 100644 index 0000000..8d5bce8 --- /dev/null +++ b/src/main/java/ru/ulstu/user/model/User.java @@ -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 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 getRoles() { + return roles; + } + + public void setRoles(Collection roles) { + this.roles.clear(); + this.roles.addAll(roles); + } +} diff --git a/src/main/java/ru/ulstu/user/model/UserDto.java b/src/main/java/ru/ulstu/user/model/UserDto.java new file mode 100644 index 0000000..c98e0bb --- /dev/null +++ b/src/main/java/ru/ulstu/user/model/UserDto.java @@ -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 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 getRoles() { + return roles; + } + + public void setRoles(Collection 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 + '\'' + + '}'; + } +} diff --git a/src/main/java/ru/ulstu/user/model/UserListDto.java b/src/main/java/ru/ulstu/user/model/UserListDto.java new file mode 100644 index 0000000..2df2174 --- /dev/null +++ b/src/main/java/ru/ulstu/user/model/UserListDto.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/user/model/UserResetPasswordDto.java b/src/main/java/ru/ulstu/user/model/UserResetPasswordDto.java new file mode 100644 index 0000000..33d84bc --- /dev/null +++ b/src/main/java/ru/ulstu/user/model/UserResetPasswordDto.java @@ -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); + } +} diff --git a/src/main/java/ru/ulstu/user/model/UserRole.java b/src/main/java/ru/ulstu/user/model/UserRole.java new file mode 100644 index 0000000..dac996a --- /dev/null +++ b/src/main/java/ru/ulstu/user/model/UserRole.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/user/model/UserRoleConstants.java b/src/main/java/ru/ulstu/user/model/UserRoleConstants.java new file mode 100644 index 0000000..364f0d2 --- /dev/null +++ b/src/main/java/ru/ulstu/user/model/UserRoleConstants.java @@ -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"; +} diff --git a/src/main/java/ru/ulstu/user/model/UserRoleDto.java b/src/main/java/ru/ulstu/user/model/UserRoleDto.java new file mode 100644 index 0000000..970db20 --- /dev/null +++ b/src/main/java/ru/ulstu/user/model/UserRoleDto.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/user/model/UserSession.java b/src/main/java/ru/ulstu/user/model/UserSession.java new file mode 100644 index 0000000..1eb761d --- /dev/null +++ b/src/main/java/ru/ulstu/user/model/UserSession.java @@ -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(); + } +} diff --git a/src/main/java/ru/ulstu/user/model/UserSessionListDto.java b/src/main/java/ru/ulstu/user/model/UserSessionListDto.java new file mode 100644 index 0000000..ebfd653 --- /dev/null +++ b/src/main/java/ru/ulstu/user/model/UserSessionListDto.java @@ -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; + } +} diff --git a/src/main/java/ru/ulstu/user/repository/UserRepository.java b/src/main/java/ru/ulstu/user/repository/UserRepository.java new file mode 100644 index 0000000..2edc8aa --- /dev/null +++ b/src/main/java/ru/ulstu/user/repository/UserRepository.java @@ -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 findOneByActivationKey(String activationKey); + + List findAllByActivatedIsFalseAndActivationDateBefore(Date date); + + User findOneByResetKey(String resetKey); + + List 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); +} diff --git a/src/main/java/ru/ulstu/user/repository/UserRoleRepository.java b/src/main/java/ru/ulstu/user/repository/UserRoleRepository.java new file mode 100644 index 0000000..098bda6 --- /dev/null +++ b/src/main/java/ru/ulstu/user/repository/UserRoleRepository.java @@ -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 { +} diff --git a/src/main/java/ru/ulstu/user/repository/UserSessionRepository.java b/src/main/java/ru/ulstu/user/repository/UserSessionRepository.java new file mode 100644 index 0000000..b922e4f --- /dev/null +++ b/src/main/java/ru/ulstu/user/repository/UserSessionRepository.java @@ -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 findOneBySessionId(String sessionId); + + List findAllByLogoutTimeIsNullAndLoginTimeBefore(Date date); +} diff --git a/src/main/java/ru/ulstu/user/scheduler/UserScheduler.java b/src/main/java/ru/ulstu/user/scheduler/UserScheduler.java new file mode 100644 index 0000000..bcad354 --- /dev/null +++ b/src/main/java/ru/ulstu/user/scheduler/UserScheduler.java @@ -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 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 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"); + } +} diff --git a/src/main/java/ru/ulstu/user/scheduler/UserSessionScheduler.java b/src/main/java/ru/ulstu/user/scheduler/UserSessionScheduler.java new file mode 100644 index 0000000..3134fe5 --- /dev/null +++ b/src/main/java/ru/ulstu/user/scheduler/UserSessionScheduler.java @@ -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 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"); + } +} diff --git a/src/main/java/ru/ulstu/user/service/MailService.java b/src/main/java/ru/ulstu/user/service/MailService.java new file mode 100644 index 0000000..0708450 --- /dev/null +++ b/src/main/java/ru/ulstu/user/service/MailService.java @@ -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); + } +} diff --git a/src/main/java/ru/ulstu/user/service/UserMapper.java b/src/main/java/ru/ulstu/user/service/UserMapper.java new file mode 100644 index 0000000..5607a26 --- /dev/null +++ b/src/main/java/ru/ulstu/user/service/UserMapper.java @@ -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 rolesFromDto(Set 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 userEntitiesToUserListDtos(List 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 roles = this.rolesFromDto(userDto.getRoles()); + if (!roles.isEmpty()) { + user.setRoles(roles); + } + return user; + } +} diff --git a/src/main/java/ru/ulstu/user/service/UserService.java b/src/main/java/ru/ulstu/user/service/UserService.java new file mode 100644 index 0000000..0c51e5a --- /dev/null +++ b/src/main/java/ru/ulstu/user/service/UserService.java @@ -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 getAllUsers(int offset, int count) { + final Page page = userRepository.findAll(new OffsetablePageRequest(offset, count, new Sort("id"))); + return new PageableItems<>(page.getTotalElements(), userMapper.userEntitiesToUserListDtos(page.getContent())); + } + + @Transactional(readOnly = true) + public PageableItems getUserRoles() { + final List 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 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())); + } +} diff --git a/src/main/java/ru/ulstu/user/service/UserSessionService.java b/src/main/java/ru/ulstu/user/service/UserSessionService.java new file mode 100644 index 0000000..36479a9 --- /dev/null +++ b/src/main/java/ru/ulstu/user/service/UserSessionService.java @@ -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 getSessions(int offset, int count) { + final Page 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); + } +} diff --git a/src/main/java/ru/ulstu/user/util/UserUtils.java b/src/main/java/ru/ulstu/user/util/UserUtils.java new file mode 100644 index 0000000..de585a5 --- /dev/null +++ b/src/main/java/ru/ulstu/user/util/UserUtils.java @@ -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; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..8b7222a --- /dev/null +++ b/src/main/resources/application.properties @@ -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 \ No newline at end of file diff --git a/src/main/resources/commits.log b/src/main/resources/commits.log new file mode 100644 index 0000000..cb97503 --- /dev/null +++ b/src/main/resources/commits.log @@ -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 \ No newline at end of file diff --git a/src/main/resources/db/changelog-20180301_130000-schema.xml b/src/main/resources/db/changelog-20180301_130000-schema.xml new file mode 100644 index 0000000..47c29e2 --- /dev/null +++ b/src/main/resources/db/changelog-20180301_130000-schema.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20180301_140000-data.xml b/src/main/resources/db/changelog-20180301_140000-data.xml new file mode 100644 index 0000000..6d23cba --- /dev/null +++ b/src/main/resources/db/changelog-20180301_140000-data.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20180301_140000-schema.xml b/src/main/resources/db/changelog-20180301_140000-schema.xml new file mode 100644 index 0000000..07ab917 --- /dev/null +++ b/src/main/resources/db/changelog-20180301_140000-schema.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20180305_100000-schema.xml b/src/main/resources/db/changelog-20180305_100000-schema.xml new file mode 100644 index 0000000..a9e1577 --- /dev/null +++ b/src/main/resources/db/changelog-20180305_100000-schema.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20180321_193000-data.xml b/src/main/resources/db/changelog-20180321_193000-data.xml new file mode 100644 index 0000000..d4d9812 --- /dev/null +++ b/src/main/resources/db/changelog-20180321_193000-data.xml @@ -0,0 +1,11 @@ + + + + + update users + set password_hash='$2a$10$5UCtAX/UcNSAcLnHUJDqUO6GR4hyPwCzBfuFI81nsoaYkvNF9SGxG' where id = 1; + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog-20180405_110000-schema.xml b/src/main/resources/db/changelog-20180405_110000-schema.xml new file mode 100644 index 0000000..e174300 --- /dev/null +++ b/src/main/resources/db/changelog-20180405_110000-schema.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20180428_110000-schema.xml b/src/main/resources/db/changelog-20180428_110000-schema.xml new file mode 100644 index 0000000..2f1917f --- /dev/null +++ b/src/main/resources/db/changelog-20180428_110000-schema.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-master.xml b/src/main/resources/db/changelog-master.xml new file mode 100644 index 0000000..713679c --- /dev/null +++ b/src/main/resources/db/changelog-master.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mail_templates/activationEmail.html b/src/main/resources/mail_templates/activationEmail.html new file mode 100644 index 0000000..053c795 --- /dev/null +++ b/src/main/resources/mail_templates/activationEmail.html @@ -0,0 +1,25 @@ + + + + Account activation + + + + +

+ Dear Ivan Ivanov +

+

+ Your account has been created, please click on the URL below to activate it: +

+

+ Activation Link +

+

+ Regards, +
+ Balance Team. +

+ + diff --git a/src/main/resources/mail_templates/passwordResetEmail.html b/src/main/resources/mail_templates/passwordResetEmail.html new file mode 100644 index 0000000..a1034d4 --- /dev/null +++ b/src/main/resources/mail_templates/passwordResetEmail.html @@ -0,0 +1,25 @@ + + + + Password reset + + + + +

+ Dear Ivan Ivanov +

+

+ For your account a password reset was requested, please click on the URL below to +

+

+ Reset Link +

+

+ Regards, +
+ Balance Team. +

+ + diff --git a/css/agency.css b/src/main/resources/public/css/agency.css similarity index 100% rename from css/agency.css rename to src/main/resources/public/css/agency.css diff --git a/css/agency.min.css b/src/main/resources/public/css/agency.min.css similarity index 100% rename from css/agency.min.css rename to src/main/resources/public/css/agency.min.css diff --git a/img/_header-bg.jpg b/src/main/resources/public/img/_header-bg.jpg similarity index 100% rename from img/_header-bg.jpg rename to src/main/resources/public/img/_header-bg.jpg diff --git a/img/about/1.jpg b/src/main/resources/public/img/about/1.jpg similarity index 100% rename from img/about/1.jpg rename to src/main/resources/public/img/about/1.jpg diff --git a/img/about/2.jpg b/src/main/resources/public/img/about/2.jpg similarity index 100% rename from img/about/2.jpg rename to src/main/resources/public/img/about/2.jpg diff --git a/img/about/3.jpg b/src/main/resources/public/img/about/3.jpg similarity index 100% rename from img/about/3.jpg rename to src/main/resources/public/img/about/3.jpg diff --git a/img/about/4.jpg b/src/main/resources/public/img/about/4.jpg similarity index 100% rename from img/about/4.jpg rename to src/main/resources/public/img/about/4.jpg diff --git a/img/header-bg.jpg b/src/main/resources/public/img/header-bg.jpg similarity index 100% rename from img/header-bg.jpg rename to src/main/resources/public/img/header-bg.jpg diff --git a/img/main/career.jpg b/src/main/resources/public/img/main/career.jpg similarity index 100% rename from img/main/career.jpg rename to src/main/resources/public/img/main/career.jpg diff --git a/img/main/conf.jpg b/src/main/resources/public/img/main/conf.jpg similarity index 100% rename from img/main/conf.jpg rename to src/main/resources/public/img/main/conf.jpg diff --git a/img/main/grants.jpg b/src/main/resources/public/img/main/grants.jpg similarity index 100% rename from img/main/grants.jpg rename to src/main/resources/public/img/main/grants.jpg diff --git a/img/main/papers.jpg b/src/main/resources/public/img/main/papers.jpg similarity index 100% rename from img/main/papers.jpg rename to src/main/resources/public/img/main/papers.jpg diff --git a/img/main/projects.jpg b/src/main/resources/public/img/main/projects.jpg similarity index 100% rename from img/main/projects.jpg rename to src/main/resources/public/img/main/projects.jpg diff --git a/img/main/students.jpg b/src/main/resources/public/img/main/students.jpg similarity index 100% rename from img/main/students.jpg rename to src/main/resources/public/img/main/students.jpg diff --git a/img/main/tasks.jpg b/src/main/resources/public/img/main/tasks.jpg similarity index 100% rename from img/main/tasks.jpg rename to src/main/resources/public/img/main/tasks.jpg diff --git a/img/main/team.jpg b/src/main/resources/public/img/main/team.jpg similarity index 100% rename from img/main/team.jpg rename to src/main/resources/public/img/main/team.jpg diff --git a/img/main/templates.jpg b/src/main/resources/public/img/main/templates.jpg similarity index 100% rename from img/main/templates.jpg rename to src/main/resources/public/img/main/templates.jpg diff --git a/img/map-image.png b/src/main/resources/public/img/map-image.png similarity index 100% rename from img/map-image.png rename to src/main/resources/public/img/map-image.png diff --git a/js/agency.js b/src/main/resources/public/js/agency.js similarity index 100% rename from js/agency.js rename to src/main/resources/public/js/agency.js diff --git a/js/agency.min.js b/src/main/resources/public/js/agency.min.js similarity index 100% rename from js/agency.min.js rename to src/main/resources/public/js/agency.min.js diff --git a/js/contact_me.js b/src/main/resources/public/js/contact_me.js similarity index 100% rename from js/contact_me.js rename to src/main/resources/public/js/contact_me.js diff --git a/js/contact_me.min.js b/src/main/resources/public/js/contact_me.min.js similarity index 100% rename from js/contact_me.min.js rename to src/main/resources/public/js/contact_me.min.js diff --git a/js/jqBootstrapValidation.js b/src/main/resources/public/js/jqBootstrapValidation.js similarity index 100% rename from js/jqBootstrapValidation.js rename to src/main/resources/public/js/jqBootstrapValidation.js diff --git a/js/jqBootstrapValidation.min.js b/src/main/resources/public/js/jqBootstrapValidation.min.js similarity index 100% rename from js/jqBootstrapValidation.min.js rename to src/main/resources/public/js/jqBootstrapValidation.min.js diff --git a/mail/contact_me.php b/src/main/resources/public/mail/contact_me.php similarity index 100% rename from mail/contact_me.php rename to src/main/resources/public/mail/contact_me.php diff --git a/scss/_contact.scss b/src/main/resources/public/scss/_contact.scss similarity index 100% rename from scss/_contact.scss rename to src/main/resources/public/scss/_contact.scss diff --git a/scss/_footer.scss b/src/main/resources/public/scss/_footer.scss similarity index 100% rename from scss/_footer.scss rename to src/main/resources/public/scss/_footer.scss diff --git a/scss/_global.scss b/src/main/resources/public/scss/_global.scss similarity index 100% rename from scss/_global.scss rename to src/main/resources/public/scss/_global.scss diff --git a/scss/_masthead.scss b/src/main/resources/public/scss/_masthead.scss similarity index 100% rename from scss/_masthead.scss rename to src/main/resources/public/scss/_masthead.scss diff --git a/scss/_mixins.scss b/src/main/resources/public/scss/_mixins.scss similarity index 100% rename from scss/_mixins.scss rename to src/main/resources/public/scss/_mixins.scss diff --git a/scss/_navbar.scss b/src/main/resources/public/scss/_navbar.scss similarity index 100% rename from scss/_navbar.scss rename to src/main/resources/public/scss/_navbar.scss diff --git a/scss/_portfolio.scss b/src/main/resources/public/scss/_portfolio.scss similarity index 100% rename from scss/_portfolio.scss rename to src/main/resources/public/scss/_portfolio.scss diff --git a/scss/_services.scss b/src/main/resources/public/scss/_services.scss similarity index 100% rename from scss/_services.scss rename to src/main/resources/public/scss/_services.scss diff --git a/scss/_team.scss b/src/main/resources/public/scss/_team.scss similarity index 100% rename from scss/_team.scss rename to src/main/resources/public/scss/_team.scss diff --git a/scss/_timeline.scss b/src/main/resources/public/scss/_timeline.scss similarity index 100% rename from scss/_timeline.scss rename to src/main/resources/public/scss/_timeline.scss diff --git a/scss/_variables.scss b/src/main/resources/public/scss/_variables.scss similarity index 100% rename from scss/_variables.scss rename to src/main/resources/public/scss/_variables.scss diff --git a/scss/agency.scss b/src/main/resources/public/scss/agency.scss similarity index 100% rename from scss/agency.scss rename to src/main/resources/public/scss/agency.scss diff --git a/vendor/bootstrap/css/bootstrap.css b/src/main/resources/public/vendor/bootstrap/css/bootstrap.css similarity index 100% rename from vendor/bootstrap/css/bootstrap.css rename to src/main/resources/public/vendor/bootstrap/css/bootstrap.css diff --git a/vendor/bootstrap/css/bootstrap.css.map b/src/main/resources/public/vendor/bootstrap/css/bootstrap.css.map similarity index 100% rename from vendor/bootstrap/css/bootstrap.css.map rename to src/main/resources/public/vendor/bootstrap/css/bootstrap.css.map diff --git a/vendor/bootstrap/css/bootstrap.min.css b/src/main/resources/public/vendor/bootstrap/css/bootstrap.min.css similarity index 100% rename from vendor/bootstrap/css/bootstrap.min.css rename to src/main/resources/public/vendor/bootstrap/css/bootstrap.min.css diff --git a/vendor/bootstrap/css/bootstrap.min.css.map b/src/main/resources/public/vendor/bootstrap/css/bootstrap.min.css.map similarity index 100% rename from vendor/bootstrap/css/bootstrap.min.css.map rename to src/main/resources/public/vendor/bootstrap/css/bootstrap.min.css.map diff --git a/vendor/bootstrap/js/bootstrap.bundle.js b/src/main/resources/public/vendor/bootstrap/js/bootstrap.bundle.js similarity index 100% rename from vendor/bootstrap/js/bootstrap.bundle.js rename to src/main/resources/public/vendor/bootstrap/js/bootstrap.bundle.js diff --git a/vendor/bootstrap/js/bootstrap.bundle.js.map b/src/main/resources/public/vendor/bootstrap/js/bootstrap.bundle.js.map similarity index 100% rename from vendor/bootstrap/js/bootstrap.bundle.js.map rename to src/main/resources/public/vendor/bootstrap/js/bootstrap.bundle.js.map diff --git a/vendor/bootstrap/js/bootstrap.bundle.min.js b/src/main/resources/public/vendor/bootstrap/js/bootstrap.bundle.min.js similarity index 100% rename from vendor/bootstrap/js/bootstrap.bundle.min.js rename to src/main/resources/public/vendor/bootstrap/js/bootstrap.bundle.min.js diff --git a/vendor/bootstrap/js/bootstrap.bundle.min.js.map b/src/main/resources/public/vendor/bootstrap/js/bootstrap.bundle.min.js.map similarity index 100% rename from vendor/bootstrap/js/bootstrap.bundle.min.js.map rename to src/main/resources/public/vendor/bootstrap/js/bootstrap.bundle.min.js.map diff --git a/vendor/bootstrap/js/bootstrap.js b/src/main/resources/public/vendor/bootstrap/js/bootstrap.js similarity index 100% rename from vendor/bootstrap/js/bootstrap.js rename to src/main/resources/public/vendor/bootstrap/js/bootstrap.js diff --git a/vendor/bootstrap/js/bootstrap.js.map b/src/main/resources/public/vendor/bootstrap/js/bootstrap.js.map similarity index 100% rename from vendor/bootstrap/js/bootstrap.js.map rename to src/main/resources/public/vendor/bootstrap/js/bootstrap.js.map diff --git a/vendor/bootstrap/js/bootstrap.min.js b/src/main/resources/public/vendor/bootstrap/js/bootstrap.min.js similarity index 100% rename from vendor/bootstrap/js/bootstrap.min.js rename to src/main/resources/public/vendor/bootstrap/js/bootstrap.min.js diff --git a/vendor/bootstrap/js/bootstrap.min.js.map b/src/main/resources/public/vendor/bootstrap/js/bootstrap.min.js.map similarity index 100% rename from vendor/bootstrap/js/bootstrap.min.js.map rename to src/main/resources/public/vendor/bootstrap/js/bootstrap.min.js.map diff --git a/vendor/font-awesome/css/font-awesome.css b/src/main/resources/public/vendor/font-awesome/css/font-awesome.css similarity index 100% rename from vendor/font-awesome/css/font-awesome.css rename to src/main/resources/public/vendor/font-awesome/css/font-awesome.css diff --git a/vendor/font-awesome/css/font-awesome.css.map b/src/main/resources/public/vendor/font-awesome/css/font-awesome.css.map similarity index 100% rename from vendor/font-awesome/css/font-awesome.css.map rename to src/main/resources/public/vendor/font-awesome/css/font-awesome.css.map diff --git a/vendor/font-awesome/css/font-awesome.min.css b/src/main/resources/public/vendor/font-awesome/css/font-awesome.min.css similarity index 100% rename from vendor/font-awesome/css/font-awesome.min.css rename to src/main/resources/public/vendor/font-awesome/css/font-awesome.min.css diff --git a/vendor/font-awesome/fonts/FontAwesome.otf b/src/main/resources/public/vendor/font-awesome/fonts/FontAwesome.otf similarity index 100% rename from vendor/font-awesome/fonts/FontAwesome.otf rename to src/main/resources/public/vendor/font-awesome/fonts/FontAwesome.otf diff --git a/vendor/font-awesome/fonts/fontawesome-webfont.eot b/src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.eot similarity index 100% rename from vendor/font-awesome/fonts/fontawesome-webfont.eot rename to src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.eot diff --git a/vendor/font-awesome/fonts/fontawesome-webfont.svg b/src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.svg similarity index 100% rename from vendor/font-awesome/fonts/fontawesome-webfont.svg rename to src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.svg diff --git a/vendor/font-awesome/fonts/fontawesome-webfont.ttf b/src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.ttf similarity index 100% rename from vendor/font-awesome/fonts/fontawesome-webfont.ttf rename to src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.ttf diff --git a/vendor/font-awesome/fonts/fontawesome-webfont.woff b/src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.woff similarity index 100% rename from vendor/font-awesome/fonts/fontawesome-webfont.woff rename to src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.woff diff --git a/vendor/font-awesome/fonts/fontawesome-webfont.woff2 b/src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.woff2 similarity index 100% rename from vendor/font-awesome/fonts/fontawesome-webfont.woff2 rename to src/main/resources/public/vendor/font-awesome/fonts/fontawesome-webfont.woff2 diff --git a/vendor/font-awesome/less/animated.less b/src/main/resources/public/vendor/font-awesome/less/animated.less similarity index 100% rename from vendor/font-awesome/less/animated.less rename to src/main/resources/public/vendor/font-awesome/less/animated.less diff --git a/vendor/font-awesome/less/bordered-pulled.less b/src/main/resources/public/vendor/font-awesome/less/bordered-pulled.less similarity index 100% rename from vendor/font-awesome/less/bordered-pulled.less rename to src/main/resources/public/vendor/font-awesome/less/bordered-pulled.less diff --git a/vendor/font-awesome/less/core.less b/src/main/resources/public/vendor/font-awesome/less/core.less similarity index 100% rename from vendor/font-awesome/less/core.less rename to src/main/resources/public/vendor/font-awesome/less/core.less diff --git a/vendor/font-awesome/less/fixed-width.less b/src/main/resources/public/vendor/font-awesome/less/fixed-width.less similarity index 100% rename from vendor/font-awesome/less/fixed-width.less rename to src/main/resources/public/vendor/font-awesome/less/fixed-width.less diff --git a/vendor/font-awesome/less/font-awesome.less b/src/main/resources/public/vendor/font-awesome/less/font-awesome.less similarity index 100% rename from vendor/font-awesome/less/font-awesome.less rename to src/main/resources/public/vendor/font-awesome/less/font-awesome.less diff --git a/vendor/font-awesome/less/icons.less b/src/main/resources/public/vendor/font-awesome/less/icons.less similarity index 100% rename from vendor/font-awesome/less/icons.less rename to src/main/resources/public/vendor/font-awesome/less/icons.less diff --git a/vendor/font-awesome/less/larger.less b/src/main/resources/public/vendor/font-awesome/less/larger.less similarity index 100% rename from vendor/font-awesome/less/larger.less rename to src/main/resources/public/vendor/font-awesome/less/larger.less diff --git a/vendor/font-awesome/less/list.less b/src/main/resources/public/vendor/font-awesome/less/list.less similarity index 100% rename from vendor/font-awesome/less/list.less rename to src/main/resources/public/vendor/font-awesome/less/list.less diff --git a/vendor/font-awesome/less/mixins.less b/src/main/resources/public/vendor/font-awesome/less/mixins.less similarity index 100% rename from vendor/font-awesome/less/mixins.less rename to src/main/resources/public/vendor/font-awesome/less/mixins.less diff --git a/vendor/font-awesome/less/path.less b/src/main/resources/public/vendor/font-awesome/less/path.less similarity index 100% rename from vendor/font-awesome/less/path.less rename to src/main/resources/public/vendor/font-awesome/less/path.less diff --git a/vendor/font-awesome/less/rotated-flipped.less b/src/main/resources/public/vendor/font-awesome/less/rotated-flipped.less similarity index 100% rename from vendor/font-awesome/less/rotated-flipped.less rename to src/main/resources/public/vendor/font-awesome/less/rotated-flipped.less diff --git a/vendor/font-awesome/less/screen-reader.less b/src/main/resources/public/vendor/font-awesome/less/screen-reader.less similarity index 100% rename from vendor/font-awesome/less/screen-reader.less rename to src/main/resources/public/vendor/font-awesome/less/screen-reader.less diff --git a/vendor/font-awesome/less/stacked.less b/src/main/resources/public/vendor/font-awesome/less/stacked.less similarity index 100% rename from vendor/font-awesome/less/stacked.less rename to src/main/resources/public/vendor/font-awesome/less/stacked.less diff --git a/vendor/font-awesome/less/variables.less b/src/main/resources/public/vendor/font-awesome/less/variables.less similarity index 100% rename from vendor/font-awesome/less/variables.less rename to src/main/resources/public/vendor/font-awesome/less/variables.less diff --git a/vendor/font-awesome/scss/_animated.scss b/src/main/resources/public/vendor/font-awesome/scss/_animated.scss similarity index 100% rename from vendor/font-awesome/scss/_animated.scss rename to src/main/resources/public/vendor/font-awesome/scss/_animated.scss diff --git a/vendor/font-awesome/scss/_bordered-pulled.scss b/src/main/resources/public/vendor/font-awesome/scss/_bordered-pulled.scss similarity index 100% rename from vendor/font-awesome/scss/_bordered-pulled.scss rename to src/main/resources/public/vendor/font-awesome/scss/_bordered-pulled.scss diff --git a/vendor/font-awesome/scss/_core.scss b/src/main/resources/public/vendor/font-awesome/scss/_core.scss similarity index 100% rename from vendor/font-awesome/scss/_core.scss rename to src/main/resources/public/vendor/font-awesome/scss/_core.scss diff --git a/vendor/font-awesome/scss/_fixed-width.scss b/src/main/resources/public/vendor/font-awesome/scss/_fixed-width.scss similarity index 100% rename from vendor/font-awesome/scss/_fixed-width.scss rename to src/main/resources/public/vendor/font-awesome/scss/_fixed-width.scss diff --git a/vendor/font-awesome/scss/_icons.scss b/src/main/resources/public/vendor/font-awesome/scss/_icons.scss similarity index 100% rename from vendor/font-awesome/scss/_icons.scss rename to src/main/resources/public/vendor/font-awesome/scss/_icons.scss diff --git a/vendor/font-awesome/scss/_larger.scss b/src/main/resources/public/vendor/font-awesome/scss/_larger.scss similarity index 100% rename from vendor/font-awesome/scss/_larger.scss rename to src/main/resources/public/vendor/font-awesome/scss/_larger.scss diff --git a/vendor/font-awesome/scss/_list.scss b/src/main/resources/public/vendor/font-awesome/scss/_list.scss similarity index 100% rename from vendor/font-awesome/scss/_list.scss rename to src/main/resources/public/vendor/font-awesome/scss/_list.scss diff --git a/vendor/font-awesome/scss/_mixins.scss b/src/main/resources/public/vendor/font-awesome/scss/_mixins.scss similarity index 100% rename from vendor/font-awesome/scss/_mixins.scss rename to src/main/resources/public/vendor/font-awesome/scss/_mixins.scss diff --git a/vendor/font-awesome/scss/_path.scss b/src/main/resources/public/vendor/font-awesome/scss/_path.scss similarity index 100% rename from vendor/font-awesome/scss/_path.scss rename to src/main/resources/public/vendor/font-awesome/scss/_path.scss diff --git a/vendor/font-awesome/scss/_rotated-flipped.scss b/src/main/resources/public/vendor/font-awesome/scss/_rotated-flipped.scss similarity index 100% rename from vendor/font-awesome/scss/_rotated-flipped.scss rename to src/main/resources/public/vendor/font-awesome/scss/_rotated-flipped.scss diff --git a/vendor/font-awesome/scss/_screen-reader.scss b/src/main/resources/public/vendor/font-awesome/scss/_screen-reader.scss similarity index 100% rename from vendor/font-awesome/scss/_screen-reader.scss rename to src/main/resources/public/vendor/font-awesome/scss/_screen-reader.scss diff --git a/vendor/font-awesome/scss/_stacked.scss b/src/main/resources/public/vendor/font-awesome/scss/_stacked.scss similarity index 100% rename from vendor/font-awesome/scss/_stacked.scss rename to src/main/resources/public/vendor/font-awesome/scss/_stacked.scss diff --git a/vendor/font-awesome/scss/_variables.scss b/src/main/resources/public/vendor/font-awesome/scss/_variables.scss similarity index 100% rename from vendor/font-awesome/scss/_variables.scss rename to src/main/resources/public/vendor/font-awesome/scss/_variables.scss diff --git a/vendor/font-awesome/scss/font-awesome.scss b/src/main/resources/public/vendor/font-awesome/scss/font-awesome.scss similarity index 100% rename from vendor/font-awesome/scss/font-awesome.scss rename to src/main/resources/public/vendor/font-awesome/scss/font-awesome.scss diff --git a/vendor/google/droid.css b/src/main/resources/public/vendor/google/droid.css similarity index 100% rename from vendor/google/droid.css rename to src/main/resources/public/vendor/google/droid.css diff --git a/vendor/google/kaushan.css b/src/main/resources/public/vendor/google/kaushan.css similarity index 100% rename from vendor/google/kaushan.css rename to src/main/resources/public/vendor/google/kaushan.css diff --git a/vendor/google/montserrat.css b/src/main/resources/public/vendor/google/montserrat.css similarity index 100% rename from vendor/google/montserrat.css rename to src/main/resources/public/vendor/google/montserrat.css diff --git a/vendor/google/roboto.css b/src/main/resources/public/vendor/google/roboto.css similarity index 100% rename from vendor/google/roboto.css rename to src/main/resources/public/vendor/google/roboto.css diff --git a/vendor/jquery-easing/jquery.easing.compatibility.js b/src/main/resources/public/vendor/jquery-easing/jquery.easing.compatibility.js similarity index 100% rename from vendor/jquery-easing/jquery.easing.compatibility.js rename to src/main/resources/public/vendor/jquery-easing/jquery.easing.compatibility.js diff --git a/vendor/jquery-easing/jquery.easing.js b/src/main/resources/public/vendor/jquery-easing/jquery.easing.js similarity index 100% rename from vendor/jquery-easing/jquery.easing.js rename to src/main/resources/public/vendor/jquery-easing/jquery.easing.js diff --git a/vendor/jquery-easing/jquery.easing.min.js b/src/main/resources/public/vendor/jquery-easing/jquery.easing.min.js similarity index 100% rename from vendor/jquery-easing/jquery.easing.min.js rename to src/main/resources/public/vendor/jquery-easing/jquery.easing.min.js diff --git a/vendor/jquery/jquery.js b/src/main/resources/public/vendor/jquery/jquery.js similarity index 100% rename from vendor/jquery/jquery.js rename to src/main/resources/public/vendor/jquery/jquery.js diff --git a/vendor/jquery/jquery.min.js b/src/main/resources/public/vendor/jquery/jquery.min.js similarity index 100% rename from vendor/jquery/jquery.min.js rename to src/main/resources/public/vendor/jquery/jquery.min.js diff --git a/vendor/jquery/jquery.min.map b/src/main/resources/public/vendor/jquery/jquery.min.map similarity index 100% rename from vendor/jquery/jquery.min.map rename to src/main/resources/public/vendor/jquery/jquery.min.map diff --git a/vendor/jquery/jquery.slim.js b/src/main/resources/public/vendor/jquery/jquery.slim.js similarity index 100% rename from vendor/jquery/jquery.slim.js rename to src/main/resources/public/vendor/jquery/jquery.slim.js diff --git a/vendor/jquery/jquery.slim.min.js b/src/main/resources/public/vendor/jquery/jquery.slim.min.js similarity index 100% rename from vendor/jquery/jquery.slim.min.js rename to src/main/resources/public/vendor/jquery/jquery.slim.min.js diff --git a/vendor/jquery/jquery.slim.min.map b/src/main/resources/public/vendor/jquery/jquery.slim.min.map similarity index 100% rename from vendor/jquery/jquery.slim.min.map rename to src/main/resources/public/vendor/jquery/jquery.slim.min.map diff --git a/src/main/resources/sample.jks b/src/main/resources/sample.jks new file mode 100644 index 0000000..6aa9a28 Binary files /dev/null and b/src/main/resources/sample.jks differ diff --git a/src/main/resources/static/favicon.ico b/src/main/resources/static/favicon.ico new file mode 100644 index 0000000..d73fd0d Binary files /dev/null and b/src/main/resources/static/favicon.ico differ diff --git a/src/main/resources/static/favicon_big.ico b/src/main/resources/static/favicon_big.ico new file mode 100644 index 0000000..94e3bc0 Binary files /dev/null and b/src/main/resources/static/favicon_big.ico differ diff --git a/src/main/resources/templates/activate.html b/src/main/resources/templates/activate.html new file mode 100644 index 0000000..b20a284 --- /dev/null +++ b/src/main/resources/templates/activate.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/admin/commits.html b/src/main/resources/templates/admin/commits.html new file mode 100644 index 0000000..a8b8e85 --- /dev/null +++ b/src/main/resources/templates/admin/commits.html @@ -0,0 +1,24 @@ + + + + + +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/admin/userList.html b/src/main/resources/templates/admin/userList.html new file mode 100644 index 0000000..b47f720 --- /dev/null +++ b/src/main/resources/templates/admin/userList.html @@ -0,0 +1,24 @@ + + + + + +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/admin/userSessions.html b/src/main/resources/templates/admin/userSessions.html new file mode 100644 index 0000000..2c3405a --- /dev/null +++ b/src/main/resources/templates/admin/userSessions.html @@ -0,0 +1,24 @@ + + + + + +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/default.html b/src/main/resources/templates/default.html new file mode 100644 index 0000000..826623b --- /dev/null +++ b/src/main/resources/templates/default.html @@ -0,0 +1,61 @@ + + + + + + + + + + NG-Tacker + + + + + + + + + + + + + + + + + + + + +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/error/403.html b/src/main/resources/templates/error/403.html new file mode 100644 index 0000000..2a64877 --- /dev/null +++ b/src/main/resources/templates/error/403.html @@ -0,0 +1,13 @@ + + + + + +
+

Доступ запрещён

+

Вернуться на главную

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/error/404.html b/src/main/resources/templates/error/404.html new file mode 100644 index 0000000..9cb1cd2 --- /dev/null +++ b/src/main/resources/templates/error/404.html @@ -0,0 +1,13 @@ + + + + + +
+

Страница не найдена

+

Вернуться на главную

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/error/500.html b/src/main/resources/templates/error/500.html new file mode 100644 index 0000000..75f84a1 --- /dev/null +++ b/src/main/resources/templates/error/500.html @@ -0,0 +1,13 @@ + + + + + +
+

Ошибка сервера

+

Вернуться на главную

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 0000000..1208764 --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,14 @@ + + + + + +
+
+ Домашняя страница +
+
+ + \ No newline at end of file diff --git a/index.html b/src/main/resources/templates/index.html similarity index 72% rename from index.html rename to src/main/resources/templates/index.html index 88d22b4..b362a4c 100644 --- a/index.html +++ b/src/main/resources/templates/index.html @@ -1,59 +1,12 @@ - - + - - - - - - - NG-Tacker - - - - - - - - - - - - - - + - - - - - +
@@ -65,13 +18,13 @@
- +
- +

Статьи

@@ -85,7 +38,7 @@
- +

Гранты

@@ -99,7 +52,7 @@
- +

Проекты

@@ -113,7 +66,7 @@
- +

Конференции

@@ -127,7 +80,7 @@
- +

Команда

@@ -141,7 +94,7 @@
- +

Работа со студентами

@@ -155,7 +108,7 @@
- +

Задачи

@@ -169,7 +122,7 @@
- +

Карьера

@@ -183,7 +136,7 @@
- +

Прочее

@@ -207,7 +160,7 @@ - +
diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..fbf9a44 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,117 @@ + + + + + + +
+ +
+
+
+
+
+ +
+
+ +
+ + +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/paper.html b/src/main/resources/templates/paper.html similarity index 100% rename from paper.html rename to src/main/resources/templates/paper.html diff --git a/src/main/resources/templates/papers.html b/src/main/resources/templates/papers.html new file mode 100644 index 0000000..107c479 --- /dev/null +++ b/src/main/resources/templates/papers.html @@ -0,0 +1,63 @@ + + + + + + + + + + diff --git a/src/main/resources/templates/reset.html b/src/main/resources/templates/reset.html new file mode 100644 index 0000000..232e260 --- /dev/null +++ b/src/main/resources/templates/reset.html @@ -0,0 +1,77 @@ + + + + + + +
+
+
+
+ +
+
+ +
+ + +
+
+ +
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/resetRequest.html b/src/main/resources/templates/resetRequest.html new file mode 100644 index 0000000..2adab37 --- /dev/null +++ b/src/main/resources/templates/resetRequest.html @@ -0,0 +1,57 @@ + + + + + + +
+
+
+
+ +
+ + +
+
+
+ + + + + \ No newline at end of file diff --git a/src/test/java/BalanceEmployeeTest.java b/src/test/java/BalanceEmployeeTest.java new file mode 100644 index 0000000..715f585 --- /dev/null +++ b/src/test/java/BalanceEmployeeTest.java @@ -0,0 +1,88 @@ +//import org.junit.jupiter.api.Test; +//import ru.ulstu.platform.balance.boundary.BalanceEmployeeService; +//import ru.ulstu.platform.balance.entity.dto.BalanceEmployeeDto; +//import ru.ulstu.platform.balance.entity.dto.BalanceValueDto; +//import ru.ulstu.platform.tools.entity.WorkType; +//import ru.ulstu.platform.unit.entity.Unit; +// +//import java.util.Arrays; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; + +//import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class BalanceEmployeeTest { + private final static Integer YEAR_2010 = 2010; + private final static Integer YEAR_2011 = 2011; + +// @Test +// public void testGetFreeEmployeesNextUnits() { +// WorkType workType = new WorkType(); +// workType.setId(1L); +// +// BalanceEmployeeService balanceEmployeeService = new BalanceEmployeeService(); +// +// Map> allUnitBalances = new HashMap<>(); +// Unit parent = new Unit(0L); +// Unit currentUnit = new Unit(1L); +// currentUnit.setParent(parent); +// Unit nextUnit = new Unit(2L); +// nextUnit.setParent(parent); +// Unit anotherUnit = new Unit(3L); +// anotherUnit.setParent(parent); +// +// parent.getChildren().addAll(Arrays.asList(currentUnit, nextUnit, anotherUnit)); +// +// Double deficit = 10.0; +// Double proficit = deficit; +// Double fullUseProfict = 0.0; +// +// //move all employees (deficit == proficit) +// BalanceValueDto balanceEmployeeValueDto = new BalanceValueDto(YEAR_2010, 1, workType, deficit * -1); +// allUnitBalances.put(currentUnit, Arrays.asList(getBalanceDto(balanceEmployeeValueDto, deficit * -1))); +// allUnitBalances.put(nextUnit, Arrays.asList(getBalanceDto(balanceEmployeeValueDto, proficit))); +// Double result = balanceEmployeeService.getFreeEmployeesNextUnits(allUnitBalances, currentUnit, balanceEmployeeValueDto); +// assertEquals(result, Double.valueOf(deficit)); +// assertEquals(Double.valueOf(fullUseProfict), Double.valueOf(allUnitBalances.get(nextUnit).get(0).getMaxValue(YEAR_2010))); +// assertEquals(Double.valueOf(fullUseProfict), Double.valueOf(allUnitBalances.get(currentUnit).get(0).getMaxValue(YEAR_2010))); +// +// //move part employees (deficit < proficit) +// Double proficitDeficitDiff = 5.0; +// proficit = deficit + proficitDeficitDiff; +// allUnitBalances.put(currentUnit, Arrays.asList(getBalanceDto(balanceEmployeeValueDto, deficit * -1))); +// allUnitBalances.put(nextUnit, Arrays.asList(getBalanceDto(balanceEmployeeValueDto, proficit))); +// result = balanceEmployeeService.getFreeEmployeesNextUnits(allUnitBalances, currentUnit, balanceEmployeeValueDto); +// assertEquals(result, Double.valueOf(deficit)); +// assertEquals(Double.valueOf(proficitDeficitDiff), Double.valueOf(allUnitBalances.get(nextUnit).get(0).getMaxValue(YEAR_2010))); +// assertEquals(Double.valueOf(fullUseProfict), Double.valueOf(allUnitBalances.get(currentUnit).get(0).getMaxValue(YEAR_2010))); +// +// //move all employees and deficit not compelte (deficit > proficit) +// proficit = deficit - proficitDeficitDiff; +// allUnitBalances.put(currentUnit, Arrays.asList(getBalanceDto(balanceEmployeeValueDto, deficit * -1))); +// allUnitBalances.put(nextUnit, Arrays.asList(getBalanceDto(balanceEmployeeValueDto, proficit))); +// result = balanceEmployeeService.getFreeEmployeesNextUnits(allUnitBalances, currentUnit, balanceEmployeeValueDto); +// assertEquals(result, Double.valueOf(deficit - proficitDeficitDiff)); +// assertEquals(Double.valueOf(fullUseProfict), Double.valueOf(allUnitBalances.get(nextUnit).get(0).getMaxValue(YEAR_2010))); +// assertEquals(Double.valueOf(proficitDeficitDiff * -1.0), Double.valueOf(allUnitBalances.get(currentUnit).get(0).getMaxValue(YEAR_2010))); +// +// //collect from several units +// proficit = deficit - proficitDeficitDiff; +// allUnitBalances.put(currentUnit, Arrays.asList(getBalanceDto(balanceEmployeeValueDto, deficit * -1))); +// allUnitBalances.put(nextUnit, Arrays.asList(getBalanceDto(balanceEmployeeValueDto, proficit))); +// allUnitBalances.put(anotherUnit, Arrays.asList(getBalanceDto(balanceEmployeeValueDto, proficit))); +// result = balanceEmployeeService.getFreeEmployeesNextUnits(allUnitBalances, currentUnit, balanceEmployeeValueDto); +// assertEquals(result, Double.valueOf(deficit)); +// assertEquals(Double.valueOf(fullUseProfict), Double.valueOf(allUnitBalances.get(nextUnit).get(0).getMaxValue(YEAR_2010))); +// assertEquals(Double.valueOf(fullUseProfict), Double.valueOf(allUnitBalances.get(anotherUnit).get(0).getMaxValue(YEAR_2010))); +// assertEquals(Double.valueOf(fullUseProfict), Double.valueOf(allUnitBalances.get(currentUnit).get(0).getMaxValue(YEAR_2010))); +// } +// +// private BalanceEmployeeDto getBalanceDto(BalanceValueDto balanceEmployeeValueDto, double deficit) { +// BalanceEmployeeDto dto = new BalanceEmployeeDto(BalanceEmployeeDto.BalanceEmployeeKey.DEFICIT); +// dto.setValue(balanceEmployeeValueDto.getPeriod().getYear(), 1, balanceEmployeeValueDto.getWorkType(), deficit); +// dto.setValue(YEAR_2011, 1, balanceEmployeeValueDto.getWorkType(), 100.0); +// return dto; +// } +} diff --git a/src/test/java/XlsDocumentBuilderTest.java b/src/test/java/XlsDocumentBuilderTest.java new file mode 100644 index 0000000..8474fcf --- /dev/null +++ b/src/test/java/XlsDocumentBuilderTest.java @@ -0,0 +1,126 @@ +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.assertj.core.util.Files; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import ru.ulstu.core.error.XlsParseException; +import ru.ulstu.core.service.XlsDocumentBuilder; + +import java.io.*; + +public class XlsDocumentBuilderTest { + private final static String TEST_FILE_NAME = "testDoc.xlsx"; + private final static String TEST_XLS_FILE_NAME = "testDoc.xls"; + private final static String CELL_TEST_VALUE = "cell test value"; + + @Before + public void before() { + Files.delete(new File(TEST_FILE_NAME)); + Files.delete(new File(TEST_XLS_FILE_NAME)); + } + + @Test + public void documentSaveTest() throws IOException, XlsParseException { + File xlsFile = new File(TEST_FILE_NAME); + XlsDocumentBuilder xlsDocumentBuilder = new XlsDocumentBuilder(xlsFile); + xlsDocumentBuilder.save(); + File savedFile = new File(TEST_FILE_NAME); + XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(savedFile)); + Assert.assertEquals(workbook.getActiveSheetIndex(), 0); + } + + @Test + public void insertSheetTest() throws IOException, XlsParseException { + File xlsFile = new File(TEST_FILE_NAME); + XlsDocumentBuilder xlsDocumentBuilder = new XlsDocumentBuilder(xlsFile) + .insertNewSheet("new sheet"); + Assert.assertEquals(xlsDocumentBuilder.getSheetCount(), 2); + Assert.assertEquals(xlsDocumentBuilder.getActiveSheetIndex(), 1); + } + + @Test + public void openExistingFile() throws IOException, XlsParseException { + File xlsFile = new File(TEST_FILE_NAME); + new XlsDocumentBuilder(xlsFile) + .insertNewSheet("new sheet") + .save(); + + XlsDocumentBuilder xlsDocumentBuilder = new XlsDocumentBuilder(xlsFile); + Assert.assertEquals(xlsDocumentBuilder.getSheetCount(), 2); + } + + @Test + public void openExistingXlsFile() throws IOException, XlsParseException { + File xlsFile = new File(TEST_XLS_FILE_NAME); + Workbook workbook = new HSSFWorkbook(); + workbook.createSheet("123"); + OutputStream out = new FileOutputStream(xlsFile); + workbook.write(out); + + XlsDocumentBuilder xlsDocumentBuilder = new XlsDocumentBuilder(xlsFile); + Assert.assertEquals(xlsDocumentBuilder.getSheetCount(), 1); + } + + @Test + public void openWrongXlsFile() throws IOException, XlsParseException { + File xlsFile = new File(TEST_XLS_FILE_NAME); + OutputStream out = new FileOutputStream(xlsFile); + out.write(1); + out.close(); + + boolean wrongDocument = false; + try { + new XlsDocumentBuilder(xlsFile); + } catch (XlsParseException ex) { + wrongDocument = true; + } + Assert.assertTrue(wrongDocument); + } + + @Test + public void setActiveSheetTest() throws IOException, XlsParseException { + File xlsFile = new File(TEST_FILE_NAME); + XlsDocumentBuilder xlsDocumentBuilder = new XlsDocumentBuilder(xlsFile) + .insertNewSheet("new sheet") + .setActiveSheet(0); + Assert.assertEquals(xlsDocumentBuilder.getActiveSheetIndex(), 0); + } + + @Test + public void getRowCountTest() throws IOException, XlsParseException { + File xlsFile = new File(TEST_FILE_NAME); + XlsDocumentBuilder xlsDocumentBuilder = new XlsDocumentBuilder(xlsFile) + .insertNewSheet("new sheet"); + Assert.assertEquals(xlsDocumentBuilder.getRowCount(), 0); + xlsDocumentBuilder.setCellValue(10, 10, CELL_TEST_VALUE); + Assert.assertEquals(xlsDocumentBuilder.getRowCount(), 10); + } + + @Test + public void getColumnCountTest() throws IOException, XlsParseException { + File xlsFile = new File(TEST_FILE_NAME); + XlsDocumentBuilder xlsDocumentBuilder = new XlsDocumentBuilder(xlsFile) + .insertNewSheet("new sheet"); + Assert.assertEquals(xlsDocumentBuilder.getColumnCount(), 0); + xlsDocumentBuilder.setCellValue(10, 10, CELL_TEST_VALUE); + Assert.assertEquals(xlsDocumentBuilder.getColumnCount(), 10); + } + + @Test + public void getCellAsStringTest() throws IOException, XlsParseException { + File xlsFile = new File(TEST_FILE_NAME); + XlsDocumentBuilder xlsDocumentBuilder = new XlsDocumentBuilder(xlsFile) + .insertNewSheet("new sheet"); + xlsDocumentBuilder.setCellValue(10, 10, CELL_TEST_VALUE); + Assert.assertEquals(xlsDocumentBuilder.getCellAsString(10, 10), CELL_TEST_VALUE); + } + + @After + public void after() { + Files.delete(new File(TEST_FILE_NAME)); + Files.delete(new File(TEST_XLS_FILE_NAME)); + } +}