commit 7b46d9ea34e389722e9346e9e1fb4b37dcdcba09 Author: Aleksey Filippov Date: Thu Jun 1 23:27:33 2023 +0400 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6724fc2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +*.db \ No newline at end of file diff --git a/.run/SbappApplication (prod).run.xml b/.run/SbappApplication (prod).run.xml new file mode 100644 index 0000000..c221320 --- /dev/null +++ b/.run/SbappApplication (prod).run.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.run/SbappApplication.run.xml b/.run/SbappApplication.run.xml new file mode 100644 index 0000000..68ee244 --- /dev/null +++ b/.run/SbappApplication.run.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7a099ef --- /dev/null +++ b/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'org.springframework.boot' version '2.6.3' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'java' +} + +group = 'ru.ulstu.is' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '17' + +def profile = project.hasProperty('prod') ? 'prod' : 'dev' +println 'Current profile is ' + profile + +bootRun { + systemProperties = System.properties + systemProperty 'spring.profiles.active', profile +} + +test { + systemProperty 'spring.profiles.active', profile +} + +jar { + enabled = false +} + +repositories { + mavenCentral() +} + +dependencies { + if (profile == 'prod') { + implementation 'org.postgresql:postgresql:42.3.5' + } else { + implementation 'com.h2database:h2:2.1.210' + } + + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-devtools' + implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' + + implementation 'org.webjars:bootstrap:5.1.3' + implementation 'org.webjars:jquery:3.6.0' + implementation 'org.webjars:font-awesome:6.1.0' + + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.liquibase:liquibase-core:4.10.0' + + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' + + implementation 'org.hibernate.validator:hibernate-validator' + + implementation 'org.springdoc:springdoc-openapi-ui:1.6.5' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 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..2e6e589 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${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='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@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 execute + +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 execute + +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 + +: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 %* + +: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/readme.md b/readme.md new file mode 100644 index 0000000..35c6c94 --- /dev/null +++ b/readme.md @@ -0,0 +1,2 @@ +createdb -U postgres sbapp +gradlew bootRun -Pprod \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..37c076c --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'sbapp' diff --git a/src/main/java/ru/ulstu/is/sbapp/SbappApplication.java b/src/main/java/ru/ulstu/is/sbapp/SbappApplication.java new file mode 100644 index 0000000..4356862 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/SbappApplication.java @@ -0,0 +1,11 @@ +package ru.ulstu.is.sbapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SbappApplication { + public static void main(String[] args) { + SpringApplication.run(SbappApplication.class, args); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java new file mode 100644 index 0000000..fadfa18 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/PasswordEncoderConfiguration.java @@ -0,0 +1,14 @@ +package ru.ulstu.is.sbapp.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfiguration { + @Bean + public PasswordEncoder createPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java new file mode 100644 index 0000000..eb31dd2 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/SecurityConfiguration.java @@ -0,0 +1,57 @@ +package ru.ulstu.is.sbapp.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +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 ru.ulstu.is.sbapp.user.controller.UserSignupMvcController; +import ru.ulstu.is.sbapp.user.service.UserService; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true) +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class); + private static final String LOGIN_URL = "/login"; + private final UserService userService; + + public SecurityConfiguration(UserService userService) { + this.userService = userService; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.headers().frameOptions().sameOrigin().and() + .cors().and() + .csrf().disable() + .authorizeRequests() + .antMatchers(UserSignupMvcController.SIGNUP_URL).permitAll() + .antMatchers(HttpMethod.GET, LOGIN_URL).permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage(LOGIN_URL).permitAll() + .and() + .logout().permitAll(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userService); + } + + @Override + public void configure(WebSecurity web) { + web.ignoring() + .antMatchers("/css/**") + .antMatchers("/js/**") + .antMatchers("/templates/**") + .antMatchers("/webjars/**"); + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java new file mode 100644 index 0000000..3a9f2fa --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/configuration/WebConfiguration.java @@ -0,0 +1,24 @@ +package ru.ulstu.is.sbapp.configuration; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfiguration implements WebMvcConfigurer { + public static final String REST_API = "/api"; + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + WebMvcConfigurer.super.addViewControllers(registry); + registry.addViewController("rest-test"); + registry.addViewController("login"); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedMethods("*"); + } +} + diff --git a/src/main/java/ru/ulstu/is/sbapp/speaker/configuration/SpeakerConfiguration.java b/src/main/java/ru/ulstu/is/sbapp/speaker/configuration/SpeakerConfiguration.java new file mode 100644 index 0000000..ee9502a --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/speaker/configuration/SpeakerConfiguration.java @@ -0,0 +1,26 @@ +package ru.ulstu.is.sbapp.speaker.configuration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.ulstu.is.sbapp.speaker.domain.Speaker; +import ru.ulstu.is.sbapp.speaker.domain.SpeakerEng; +import ru.ulstu.is.sbapp.speaker.domain.SpeakerRus; + +@Configuration +public class SpeakerConfiguration { + private final Logger log = LoggerFactory.getLogger(SpeakerConfiguration.class); + + @Bean(value = "ru", initMethod = "init", destroyMethod = "destroy") + public SpeakerRus createRusSpeaker() { + log.info("Call createRusSpeaker()"); + return new SpeakerRus(); + } + + @Bean(value = "en") + public Speaker createEngSpeaker() { + log.info("Call createEngSpeaker()"); + return new SpeakerEng(); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/speaker/controller/SpeakerController.java b/src/main/java/ru/ulstu/is/sbapp/speaker/controller/SpeakerController.java new file mode 100644 index 0000000..895a9ce --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/speaker/controller/SpeakerController.java @@ -0,0 +1,24 @@ +package ru.ulstu.is.sbapp.speaker.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.is.sbapp.configuration.WebConfiguration; +import ru.ulstu.is.sbapp.speaker.service.SpeakerService; + +@RestController +@RequestMapping(WebConfiguration.REST_API + "/speaker") +public class SpeakerController { + private final SpeakerService speakerService; + + public SpeakerController(SpeakerService speakerService) { + this.speakerService = speakerService; + } + + @GetMapping + public String hello(@RequestParam(value = "name", defaultValue = "Мир") String name, + @RequestParam(value = "lang", defaultValue = "ru") String lang) { + return speakerService.say(name, lang); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/speaker/controller/SpeakerMvcController.java b/src/main/java/ru/ulstu/is/sbapp/speaker/controller/SpeakerMvcController.java new file mode 100644 index 0000000..9c83d6a --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/speaker/controller/SpeakerMvcController.java @@ -0,0 +1,33 @@ +package ru.ulstu.is.sbapp.speaker.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import ru.ulstu.is.sbapp.speaker.service.SpeakerService; + +import java.util.List; + +@Controller +@RequestMapping("/speaker") +public class SpeakerMvcController { + private final SpeakerService speakerService; + private final List langs; + + public SpeakerMvcController(SpeakerService speakerService) { + this.speakerService = speakerService; + this.langs = List.of("ru", "en", "de", "deu"); + } + + @GetMapping + public String hello(@RequestParam(value = "name", defaultValue = "Мир") String name, + @RequestParam(value = "lang", defaultValue = "ru") String lang, + Model model) { + model.addAttribute("langs", langs); + model.addAttribute("name", name); + model.addAttribute("lang", lang); + model.addAttribute("say", speakerService.say(name, lang)); + return "speaker"; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/speaker/domain/Speaker.java b/src/main/java/ru/ulstu/is/sbapp/speaker/domain/Speaker.java new file mode 100644 index 0000000..c2f00e0 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/speaker/domain/Speaker.java @@ -0,0 +1,5 @@ +package ru.ulstu.is.sbapp.speaker.domain; + +public interface Speaker { + String say(); +} diff --git a/src/main/java/ru/ulstu/is/sbapp/speaker/domain/SpeakerDeu.java b/src/main/java/ru/ulstu/is/sbapp/speaker/domain/SpeakerDeu.java new file mode 100644 index 0000000..019ee8b --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/speaker/domain/SpeakerDeu.java @@ -0,0 +1,28 @@ +package ru.ulstu.is.sbapp.speaker.domain; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +@Component(value = "de") +public class SpeakerDeu implements Speaker { + private final Logger log = LoggerFactory.getLogger(SpeakerDeu.class); + + @Override + public String say() { + return "Hallo"; + } + + @PostConstruct + public void init() { + log.info("SpeakerDeu.init()"); + } + + @PreDestroy + public void destroy() { + log.info("SpeakerDeu.destroy()"); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/speaker/domain/SpeakerEng.java b/src/main/java/ru/ulstu/is/sbapp/speaker/domain/SpeakerEng.java new file mode 100644 index 0000000..4d3ac74 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/speaker/domain/SpeakerEng.java @@ -0,0 +1,26 @@ +package ru.ulstu.is.sbapp.speaker.domain; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; + +public class SpeakerEng implements Speaker, InitializingBean, DisposableBean { + private final Logger log = LoggerFactory.getLogger(SpeakerEng.class); + + @Override + public String say() { + return "Hello"; + } + + @Override + public void afterPropertiesSet() { + log.info("SpeakerEng.afterPropertiesSet()"); + } + + @Override + public void destroy() { + log.info("SpeakerEng.destroy()"); + + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/speaker/domain/SpeakerRus.java b/src/main/java/ru/ulstu/is/sbapp/speaker/domain/SpeakerRus.java new file mode 100644 index 0000000..3bcc366 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/speaker/domain/SpeakerRus.java @@ -0,0 +1,21 @@ +package ru.ulstu.is.sbapp.speaker.domain; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SpeakerRus implements Speaker { + private final Logger log = LoggerFactory.getLogger(SpeakerRus.class); + + @Override + public String say() { + return "Привет"; + } + + public void init() { + log.info("SpeakerRus.init()"); + } + + public void destroy() { + log.info("SpeakerRus.destroy()"); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/speaker/service/SpeakerService.java b/src/main/java/ru/ulstu/is/sbapp/speaker/service/SpeakerService.java new file mode 100644 index 0000000..de349da --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/speaker/service/SpeakerService.java @@ -0,0 +1,19 @@ +package ru.ulstu.is.sbapp.speaker.service; + +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; +import ru.ulstu.is.sbapp.speaker.domain.Speaker; + +@Service +public class SpeakerService { + private final ApplicationContext applicationContext; + + public SpeakerService(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public String say(String name, String lang) { + final Speaker speaker = (Speaker) applicationContext.getBean(lang); + return String.format("%s %s!", speaker.say(), name); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/student/controller/StudentController.java b/src/main/java/ru/ulstu/is/sbapp/student/controller/StudentController.java new file mode 100644 index 0000000..1b98a29 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/student/controller/StudentController.java @@ -0,0 +1,53 @@ +package ru.ulstu.is.sbapp.student.controller; + +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.ulstu.is.sbapp.configuration.WebConfiguration; +import ru.ulstu.is.sbapp.student.service.StudentService; + +import javax.validation.Valid; +import java.util.List; + +@RestController +@RequestMapping(WebConfiguration.REST_API + "/student") +public class StudentController { + private final StudentService studentService; + + public StudentController(StudentService studentService) { + this.studentService = studentService; + } + + @GetMapping("/{id}") + public StudentDto getStudent(@PathVariable Long id) { + return new StudentDto(studentService.findStudent(id)); + } + + @GetMapping + public List getStudents() { + return studentService.findAllStudents().stream() + .map(StudentDto::new) + .toList(); + } + + @PostMapping + public StudentDto createStudent(@RequestBody @Valid StudentDto studentDto) { + return new StudentDto(studentService.addStudent(studentDto.getFirstName(), studentDto.getLastName())); + } + + @PutMapping("/{id}") + public StudentDto updateStudent(@PathVariable Long id, + @RequestBody @Valid StudentDto studentDto) { + return new StudentDto(studentService.updateStudent(id, studentDto.getFirstName(), studentDto.getLastName())); + } + + @DeleteMapping("/{id}") + public StudentDto deleteStudent(@PathVariable Long id) { + return new StudentDto(studentService.deleteStudent(id)); + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/student/controller/StudentDto.java b/src/main/java/ru/ulstu/is/sbapp/student/controller/StudentDto.java new file mode 100644 index 0000000..441fb96 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/student/controller/StudentDto.java @@ -0,0 +1,44 @@ +package ru.ulstu.is.sbapp.student.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import ru.ulstu.is.sbapp.student.model.Student; + +import javax.validation.constraints.NotBlank; + +public class StudentDto { + private long id; + @NotBlank(message = "Firstname can't be null or empty") + private String firstName; + @NotBlank(message = "Lastname can't be null or empty") + private String lastName; + + public StudentDto() { + } + + public StudentDto(Student student) { + this.id = student.getId(); + this.firstName = student.getFirstName(); + this.lastName = student.getLastName(); + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public long getId() { + return id; + } + + 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; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/student/controller/StudentMvcController.java b/src/main/java/ru/ulstu/is/sbapp/student/controller/StudentMvcController.java new file mode 100644 index 0000000..10cf563 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/student/controller/StudentMvcController.java @@ -0,0 +1,67 @@ +package ru.ulstu.is.sbapp.student.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import ru.ulstu.is.sbapp.student.service.StudentService; + +import javax.validation.Valid; + +@Controller +@RequestMapping("/student") +public class StudentMvcController { + private final StudentService studentService; + + public StudentMvcController(StudentService studentService) { + this.studentService = studentService; + } + + @GetMapping + public String getStudents(Model model) { + model.addAttribute("students", + studentService.findAllStudents().stream() + .map(StudentDto::new) + .toList()); + return "student"; + } + + @GetMapping(value = {"/edit", "/edit/{id}"}) + public String editStudent(@PathVariable(required = false) Long id, + Model model) { + if (id == null || id <= 0) { + model.addAttribute("studentDto", new StudentDto()); + } else { + model.addAttribute("studentId", id); + model.addAttribute("studentDto", new StudentDto(studentService.findStudent(id))); + } + return "student-edit"; + } + + @PostMapping(value = {"/edit", "/edit/{id}"}) + public String saveStudent(@PathVariable(required = false) Long id, + @ModelAttribute @Valid StudentDto studentDto, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + model.addAttribute("errors", bindingResult.getAllErrors()); + return "student-edit"; + } + if (id == null || id <= 0) { + studentService.addStudent(studentDto.getFirstName(), studentDto.getLastName()); + } else { + studentService.updateStudent(id, studentDto.getFirstName(), studentDto.getLastName()); + } + return "redirect:/student"; + } + + @PostMapping("/delete/{id}") + public String deleteStudent(@PathVariable Long id) { + studentService.deleteStudent(id); + return "redirect:/student"; + } +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/is/sbapp/student/model/Student.java b/src/main/java/ru/ulstu/is/sbapp/student/model/Student.java new file mode 100644 index 0000000..f2507e5 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/student/model/Student.java @@ -0,0 +1,69 @@ +package ru.ulstu.is.sbapp.student.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.validation.constraints.NotBlank; +import java.util.Objects; + +@Entity +public class Student { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + @NotBlank(message = "Firstname can't be null or empty") + private String firstName; + @NotBlank(message = "Lastname can't be null or empty") + private String lastName; + + public Student() { + } + + public Student(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public Long getId() { + return id; + } + + 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; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Student student = (Student) o; + return Objects.equals(id, student.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Student{" + + "id=" + id + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + '}'; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/student/repository/StudentRepository.java b/src/main/java/ru/ulstu/is/sbapp/student/repository/StudentRepository.java new file mode 100644 index 0000000..80d193b --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/student/repository/StudentRepository.java @@ -0,0 +1,7 @@ +package ru.ulstu.is.sbapp.student.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.is.sbapp.student.model.Student; + +public interface StudentRepository extends JpaRepository { +} diff --git a/src/main/java/ru/ulstu/is/sbapp/student/service/StudentNotFoundException.java b/src/main/java/ru/ulstu/is/sbapp/student/service/StudentNotFoundException.java new file mode 100644 index 0000000..94696dc --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/student/service/StudentNotFoundException.java @@ -0,0 +1,7 @@ +package ru.ulstu.is.sbapp.student.service; + +public class StudentNotFoundException extends RuntimeException { + public StudentNotFoundException(Long id) { + super(String.format("Student with id [%s] is not found", id)); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/student/service/StudentService.java b/src/main/java/ru/ulstu/is/sbapp/student/service/StudentService.java new file mode 100644 index 0000000..b5ecc60 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/student/service/StudentService.java @@ -0,0 +1,61 @@ +package ru.ulstu.is.sbapp.student.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.is.sbapp.student.model.Student; +import ru.ulstu.is.sbapp.student.repository.StudentRepository; +import ru.ulstu.is.sbapp.util.validation.ValidatorUtil; + +import java.util.List; +import java.util.Optional; + +@Service +public class StudentService { + private final StudentRepository studentRepository; + private final ValidatorUtil validatorUtil; + + public StudentService(StudentRepository studentRepository, + ValidatorUtil validatorUtil) { + this.studentRepository = studentRepository; + this.validatorUtil = validatorUtil; + } + + @Transactional + public Student addStudent(String firstName, String lastName) { + final Student student = new Student(firstName, lastName); + validatorUtil.validate(student); + return studentRepository.save(student); + } + + @Transactional(readOnly = true) + public Student findStudent(Long id) { + final Optional student = studentRepository.findById(id); + return student.orElseThrow(() -> new StudentNotFoundException(id)); + } + + @Transactional(readOnly = true) + public List findAllStudents() { + return studentRepository.findAll(); + } + + @Transactional + public Student updateStudent(Long id, String firstName, String lastName) { + final Student currentStudent = findStudent(id); + currentStudent.setFirstName(firstName); + currentStudent.setLastName(lastName); + validatorUtil.validate(currentStudent); + return studentRepository.save(currentStudent); + } + + @Transactional + public Student deleteStudent(Long id) { + final Student currentStudent = findStudent(id); + studentRepository.delete(currentStudent); + return currentStudent; + } + + @Transactional + public void deleteAllStudents() { + studentRepository.deleteAll(); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/test/controller/TestController.java b/src/main/java/ru/ulstu/is/sbapp/test/controller/TestController.java new file mode 100644 index 0000000..e08a099 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/test/controller/TestController.java @@ -0,0 +1,26 @@ +package ru.ulstu.is.sbapp.test.controller; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.ulstu.is.sbapp.configuration.WebConfiguration; +import ru.ulstu.is.sbapp.test.model.TestDto; +import ru.ulstu.is.sbapp.test.service.TestService; + +import javax.validation.Valid; + +@RestController +@RequestMapping(WebConfiguration.REST_API + "/test") +public class TestController { + private final TestService testService; + + public TestController(TestService testService) { + this.testService = testService; + } + + @PostMapping + public TestDto testValidation(@RequestBody @Valid TestDto testDto) { + return testService.doSomething(testDto); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/test/controller/TestMvcController.java b/src/main/java/ru/ulstu/is/sbapp/test/controller/TestMvcController.java new file mode 100644 index 0000000..a13959e --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/test/controller/TestMvcController.java @@ -0,0 +1,41 @@ +package ru.ulstu.is.sbapp.test.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import ru.ulstu.is.sbapp.test.model.TestDto; +import ru.ulstu.is.sbapp.test.service.TestService; + +import javax.validation.Valid; + +@Controller +@RequestMapping("/test") +public class TestMvcController { + private final TestService testService; + + public TestMvcController(TestService testService) { + this.testService = testService; + } + + @GetMapping + public String test(Model model) { + model.addAttribute("testDto", new TestDto()); + return "test"; + } + + @PostMapping + public String testValidation(@ModelAttribute @Valid TestDto testDto, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + model.addAttribute("errors", bindingResult.getAllErrors()); + return "test"; + } + model.addAttribute("testDto", testService.doSomething(testDto)); + return "test-result"; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/test/model/TestDto.java b/src/main/java/ru/ulstu/is/sbapp/test/model/TestDto.java new file mode 100644 index 0000000..0e22d27 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/test/model/TestDto.java @@ -0,0 +1,40 @@ +package ru.ulstu.is.sbapp.test.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +public class TestDto { + @NotNull(message = "Id can't be null") + private Long id; + @NotBlank(message = "Name can't be null or empty") + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getData() { + return String.format("%s %s", id, name); + } + + @JsonIgnore + public String getAnotherData() { + return "Test"; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/test/service/TestService.java b/src/main/java/ru/ulstu/is/sbapp/test/service/TestService.java new file mode 100644 index 0000000..6484bcf --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/test/service/TestService.java @@ -0,0 +1,7 @@ +package ru.ulstu.is.sbapp.test.service; + +import ru.ulstu.is.sbapp.test.model.TestDto; + +public interface TestService { + TestDto doSomething(TestDto testDto); +} diff --git a/src/main/java/ru/ulstu/is/sbapp/test/service/TestServiceDevImpl.java b/src/main/java/ru/ulstu/is/sbapp/test/service/TestServiceDevImpl.java new file mode 100644 index 0000000..b698284 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/test/service/TestServiceDevImpl.java @@ -0,0 +1,20 @@ +package ru.ulstu.is.sbapp.test.service; + +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; +import ru.ulstu.is.sbapp.test.model.TestDto; + +@Service +@Profile("dev") +public class TestServiceDevImpl extends TestServiceImpl { + public TestServiceDevImpl(Environment environment) { + super(environment); + } + + @Override + public TestDto doSomething(TestDto testDto) { + log.info("TestServiceDevImpl.doSomething"); + return testDto; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/test/service/TestServiceImpl.java b/src/main/java/ru/ulstu/is/sbapp/test/service/TestServiceImpl.java new file mode 100644 index 0000000..15bbb6e --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/test/service/TestServiceImpl.java @@ -0,0 +1,27 @@ +package ru.ulstu.is.sbapp.test.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import ru.ulstu.is.sbapp.test.model.TestDto; + +abstract class TestServiceImpl implements TestService { + protected final Logger log = LoggerFactory.getLogger(TestServiceImpl.class); + + protected final Environment environment; + + TestServiceImpl(Environment environment) { + this.environment = environment; + printCurrentProfile(); + } + + private void printCurrentProfile() { + final String[] profiles = environment.getActiveProfiles().length == 0 ? + environment.getDefaultProfiles() : + environment.getActiveProfiles(); + log.warn("Current profile is {}", String.join(", ", profiles)); + } + + @Override + abstract public TestDto doSomething(TestDto testDto); +} diff --git a/src/main/java/ru/ulstu/is/sbapp/test/service/TestServiceProdImpl.java b/src/main/java/ru/ulstu/is/sbapp/test/service/TestServiceProdImpl.java new file mode 100644 index 0000000..d0f5abb --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/test/service/TestServiceProdImpl.java @@ -0,0 +1,20 @@ +package ru.ulstu.is.sbapp.test.service; + +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; +import ru.ulstu.is.sbapp.test.model.TestDto; + +@Service +@Profile("prod") +public class TestServiceProdImpl extends TestServiceImpl { + public TestServiceProdImpl(Environment environment) { + super(environment); + } + + @Override + public TestDto doSomething(TestDto testDto) { + log.info("TestServiceProdImpl.doSomething"); + return testDto; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/user/controller/UserMvcController.java b/src/main/java/ru/ulstu/is/sbapp/user/controller/UserMvcController.java new file mode 100644 index 0000000..58b4415 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/user/controller/UserMvcController.java @@ -0,0 +1,42 @@ +package ru.ulstu.is.sbapp.user.controller; + +import org.springframework.data.domain.Page; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import ru.ulstu.is.sbapp.user.model.UserDto; +import ru.ulstu.is.sbapp.user.model.UserRole; +import ru.ulstu.is.sbapp.user.service.UserService; + +import java.util.List; +import java.util.stream.IntStream; + +@Controller +@RequestMapping("/users") +public class UserMvcController { + private final UserService userService; + + public UserMvcController(UserService userService) { + this.userService = userService; + } + + @GetMapping + @Secured({UserRole.AsString.ADMIN}) + public String getUsers(@RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "5") int size, + Model model) { + final Page users = userService.findAllPages(page, size) + .map(UserDto::new); + model.addAttribute("users", users); + final int totalPages = users.getTotalPages(); + final List pageNumbers = IntStream.rangeClosed(1, totalPages) + .boxed() + .toList(); + model.addAttribute("pages", pageNumbers); + model.addAttribute("totalPages", totalPages); + return "users"; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/user/controller/UserSignupMvcController.java b/src/main/java/ru/ulstu/is/sbapp/user/controller/UserSignupMvcController.java new file mode 100644 index 0000000..675e1a5 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/user/controller/UserSignupMvcController.java @@ -0,0 +1,51 @@ +package ru.ulstu.is.sbapp.user.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import ru.ulstu.is.sbapp.user.model.User; +import ru.ulstu.is.sbapp.user.model.UserSignupDto; +import ru.ulstu.is.sbapp.user.service.UserService; +import ru.ulstu.is.sbapp.util.validation.ValidationException; + +import javax.validation.Valid; + +@Controller +@RequestMapping(UserSignupMvcController.SIGNUP_URL) +public class UserSignupMvcController { + public static final String SIGNUP_URL = "/signup"; + + private final UserService userService; + + public UserSignupMvcController(UserService userService) { + this.userService = userService; + } + + @GetMapping + public String showSignupForm(Model model) { + model.addAttribute("userDto", new UserSignupDto()); + return "signup"; + } + + @PostMapping + public String signup(@ModelAttribute("userDto") @Valid UserSignupDto userSignupDto, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + model.addAttribute("errors", bindingResult.getAllErrors()); + return "signup"; + } + try { + final User user = userService.createUser( + userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm()); + return "redirect:/login?created=" + user.getLogin(); + } catch (ValidationException e) { + model.addAttribute("errors", e.getMessage()); + return "signup"; + } + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/user/model/User.java b/src/main/java/ru/ulstu/is/sbapp/user/model/User.java new file mode 100644 index 0000000..f5a0d9a --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/user/model/User.java @@ -0,0 +1,78 @@ +package ru.ulstu.is.sbapp.user.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.Objects; + +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + @Column(nullable = false, unique = true, length = 64) + @NotBlank + @Size(min = 3, max = 64) + private String login; + @Column(nullable = false, length = 64) + @NotBlank + @Size(min = 6, max = 64) + private String password; + private UserRole role; + + public User() { + } + + public User(String login, String password) { + this(login, password, UserRole.USER); + } + + public User(String login, String password, UserRole role) { + this.login = login; + this.password = password; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public UserRole getRole() { + return role; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id) && Objects.equals(login, user.login); + } + + @Override + public int hashCode() { + return Objects.hash(id, login); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/user/model/UserDto.java b/src/main/java/ru/ulstu/is/sbapp/user/model/UserDto.java new file mode 100644 index 0000000..be7d423 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/user/model/UserDto.java @@ -0,0 +1,25 @@ +package ru.ulstu.is.sbapp.user.model; + +public class UserDto { + private final long id; + private final String login; + private final UserRole role; + + public UserDto(User user) { + this.id = user.getId(); + this.login = user.getLogin(); + this.role = user.getRole(); + } + + public long getId() { + return id; + } + + public String getLogin() { + return login; + } + + public UserRole getRole() { + return role; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/user/model/UserRole.java b/src/main/java/ru/ulstu/is/sbapp/user/model/UserRole.java new file mode 100644 index 0000000..761961f --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/user/model/UserRole.java @@ -0,0 +1,20 @@ +package ru.ulstu.is.sbapp.user.model; + +import org.springframework.security.core.GrantedAuthority; + +public enum UserRole implements GrantedAuthority { + ADMIN, + USER; + + private static final String PREFIX = "ROLE_"; + + @Override + public String getAuthority() { + return PREFIX + this.name(); + } + + public static final class AsString { + public static final String ADMIN = PREFIX + "ADMIN"; + public static final String USER = PREFIX + "USER"; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/user/model/UserSignupDto.java b/src/main/java/ru/ulstu/is/sbapp/user/model/UserSignupDto.java new file mode 100644 index 0000000..03e8b20 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/user/model/UserSignupDto.java @@ -0,0 +1,40 @@ +package ru.ulstu.is.sbapp.user.model; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +public class UserSignupDto { + @NotBlank + @Size(min = 3, max = 64) + private String login; + @NotBlank + @Size(min = 6, max = 64) + private String password; + @NotBlank + @Size(min = 6, max = 64) + private String passwordConfirm; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPasswordConfirm() { + return passwordConfirm; + } + + public void setPasswordConfirm(String passwordConfirm) { + this.passwordConfirm = passwordConfirm; + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/user/repository/UserRepository.java b/src/main/java/ru/ulstu/is/sbapp/user/repository/UserRepository.java new file mode 100644 index 0000000..40b3d42 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/user/repository/UserRepository.java @@ -0,0 +1,8 @@ +package ru.ulstu.is.sbapp.user.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.is.sbapp.user.model.User; + +public interface UserRepository extends JpaRepository { + User findOneByLoginIgnoreCase(String login); +} diff --git a/src/main/java/ru/ulstu/is/sbapp/user/service/UserService.java b/src/main/java/ru/ulstu/is/sbapp/user/service/UserService.java new file mode 100644 index 0000000..0393c47 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/user/service/UserService.java @@ -0,0 +1,67 @@ +package ru.ulstu.is.sbapp.user.service; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import ru.ulstu.is.sbapp.user.model.User; +import ru.ulstu.is.sbapp.user.model.UserRole; +import ru.ulstu.is.sbapp.user.repository.UserRepository; +import ru.ulstu.is.sbapp.util.validation.ValidationException; +import ru.ulstu.is.sbapp.util.validation.ValidatorUtil; + +import java.util.Collections; +import java.util.Objects; + +@Service +public class UserService implements UserDetailsService { + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final ValidatorUtil validatorUtil; + + public UserService(UserRepository userRepository, + PasswordEncoder passwordEncoder, + ValidatorUtil validatorUtil) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.validatorUtil = validatorUtil; + } + + public Page findAllPages(int page, int size) { + return userRepository.findAll(PageRequest.of(page - 1, size, Sort.by("id").ascending())); + } + + public User findByLogin(String login) { + return userRepository.findOneByLoginIgnoreCase(login); + } + + public User createUser(String login, String password, String passwordConfirm) { + return createUser(login, password, passwordConfirm, UserRole.USER); + } + + public User createUser(String login, String password, String passwordConfirm, UserRole role) { + if (findByLogin(login) != null) { + throw new ValidationException(String.format("User '%s' already exists", login)); + } + final User user = new User(login, passwordEncoder.encode(password), role); + validatorUtil.validate(user); + if (!Objects.equals(password, passwordConfirm)) { + throw new ValidationException("Passwords not equals"); + } + return userRepository.save(user); + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + final User userEntity = findByLogin(username); + if (userEntity == null) { + throw new UsernameNotFoundException(username); + } + return new org.springframework.security.core.userdetails.User( + userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole())); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/util/error/AdviceController.java b/src/main/java/ru/ulstu/is/sbapp/util/error/AdviceController.java new file mode 100644 index 0000000..e9a1e1c --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/util/error/AdviceController.java @@ -0,0 +1,39 @@ +package ru.ulstu.is.sbapp.util.error; + +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.is.sbapp.student.service.StudentNotFoundException; +import ru.ulstu.is.sbapp.util.validation.ValidationException; + +import java.util.stream.Collectors; + +@ControllerAdvice(annotations = RestController.class) +public class AdviceController { + @ExceptionHandler({ + StudentNotFoundException.class, + ValidationException.class + }) + public ResponseEntity handleException(Throwable e) { + return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleBindException(MethodArgumentNotValidException e) { + final ValidationException validationException = new ValidationException( + e.getBindingResult().getAllErrors().stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .collect(Collectors.toSet())); + return handleException(validationException); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleUnknownException(Throwable e) { + e.printStackTrace(); + return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/util/validation/ValidationException.java b/src/main/java/ru/ulstu/is/sbapp/util/validation/ValidationException.java new file mode 100644 index 0000000..c75d0cb --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/util/validation/ValidationException.java @@ -0,0 +1,13 @@ +package ru.ulstu.is.sbapp.util.validation; + +import java.util.Set; + +public class ValidationException extends RuntimeException { + public ValidationException(Set errors) { + super(String.join("\n", errors)); + } + + public ValidationException(String error) { + super(error); + } +} diff --git a/src/main/java/ru/ulstu/is/sbapp/util/validation/ValidatorUtil.java b/src/main/java/ru/ulstu/is/sbapp/util/validation/ValidatorUtil.java new file mode 100644 index 0000000..0b7c634 --- /dev/null +++ b/src/main/java/ru/ulstu/is/sbapp/util/validation/ValidatorUtil.java @@ -0,0 +1,27 @@ +package ru.ulstu.is.sbapp.util.validation; + +import org.springframework.stereotype.Component; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +public class ValidatorUtil { + private final Validator validator; + + public ValidatorUtil() { + this.validator = Validation.buildDefaultValidatorFactory().getValidator(); + } + + public void validate(T object) { + final Set> errors = validator.validate(object); + if (!errors.isEmpty()) { + throw new ValidationException(errors.stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.toSet())); + } + } +} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..6542b4f --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,9 @@ +# JPA +spring.datasource.url=jdbc:h2:file:./data +spring.datasource.username=sa +spring.datasource.password=password +spring.datasource.driverClassName=org.h2.Driver +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true +spring.h2.console.settings.trace=false +spring.h2.console.settings.web-allow-others=false \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 0000000..e0a632b --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -0,0 +1,7 @@ +# Dev tools +spring.devtools.add-properties=false +# JPA +spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/sbapp +spring.datasource.username=postgres +spring.datasource.password=postgres +spring.datasource.driverclassName=org.postgresql.Driver \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..4d6347c --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,11 @@ +# Server +spring.main.banner-mode=off +server.port=8080 +# Profiles +spring.profiles.default=dev +# JPA +spring.jpa.hibernate.ddl-auto=validate +# Liquibase Settings +spring.liquibase.enabled=true +spring.liquibase.drop-first=false +spring.liquibase.change-log=classpath:migrations/changelog-master.yml \ No newline at end of file diff --git a/src/main/resources/migrations/changelog-master.yml b/src/main/resources/migrations/changelog-master.yml new file mode 100644 index 0000000..6cc3769 --- /dev/null +++ b/src/main/resources/migrations/changelog-master.yml @@ -0,0 +1,13 @@ +databaseChangeLog: + - include: + file: migrations/student/student-schema-1.yml + - include: + file: migrations/user/user-schema-1.yml + - include: + file: migrations/user/user-data-1.yml + - include: + file: migrations/user/user-schema-2.yml + - include: + file: migrations/user/user-data-2.yml + - include: + file: migrations/user/user-schema-3.yml \ No newline at end of file diff --git a/src/main/resources/migrations/student/student-schema-1.yml b/src/main/resources/migrations/student/student-schema-1.yml new file mode 100644 index 0000000..aa5ce42 --- /dev/null +++ b/src/main/resources/migrations/student/student-schema-1.yml @@ -0,0 +1,24 @@ +databaseChangeLog: + - changeSet: + id: 2022-04-01-schema-1 + author: user + changes: + - createTable: + tableName: student + columns: + - column: + name: id + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: first_name + type: varchar(255) + constraints: + nullable: false + - column: + name: last_name + type: varchar(255) + constraints: + nullable: false \ No newline at end of file diff --git a/src/main/resources/migrations/user/user-data-1.yml b/src/main/resources/migrations/user/user-data-1.yml new file mode 100644 index 0000000..4d1ad56 --- /dev/null +++ b/src/main/resources/migrations/user/user-data-1.yml @@ -0,0 +1,17 @@ +databaseChangeLog: + - changeSet: + id: 2022-04-15-data-1 + author: user + changes: + - insert: + tableName: users + columns: + - column: + name: id + value: 1 + - column: + name: login + value: admin + - column: + name: password + value: $2a$10$WZg1ajfZBi1wqecE85yV..sfw8Fj9z1ZzRBszlDbNn97EKlD2jfsC \ No newline at end of file diff --git a/src/main/resources/migrations/user/user-data-2.yml b/src/main/resources/migrations/user/user-data-2.yml new file mode 100644 index 0000000..0470514 --- /dev/null +++ b/src/main/resources/migrations/user/user-data-2.yml @@ -0,0 +1,7 @@ +databaseChangeLog: + - changeSet: + id: 2022-05-01-data-2 + author: user + changes: + - sql: + sql: update users set role=0 where id = 1 \ No newline at end of file diff --git a/src/main/resources/migrations/user/user-schema-1.yml b/src/main/resources/migrations/user/user-schema-1.yml new file mode 100644 index 0000000..455076e --- /dev/null +++ b/src/main/resources/migrations/user/user-schema-1.yml @@ -0,0 +1,28 @@ +databaseChangeLog: + - changeSet: + id: 2022-04-15-schema-1 + author: user + changes: + - createTable: + tableName: users + columns: + - column: + name: id + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: login + type: varchar(64) + constraints: + nullable: false + unique: true + - column: + name: password + type: varchar(64) + constraints: + nullable: false + - createSequence: + sequenceName: hibernate_sequence + startValue: 10 \ No newline at end of file diff --git a/src/main/resources/migrations/user/user-schema-2.yml b/src/main/resources/migrations/user/user-schema-2.yml new file mode 100644 index 0000000..b078370 --- /dev/null +++ b/src/main/resources/migrations/user/user-schema-2.yml @@ -0,0 +1,11 @@ +databaseChangeLog: + - changeSet: + id: 2022-05-01-schema-1 + author: user + changes: + - addColumn: + tableName: users + columns: + - column: + name: role + type: int \ No newline at end of file diff --git a/src/main/resources/migrations/user/user-schema-3.yml b/src/main/resources/migrations/user/user-schema-3.yml new file mode 100644 index 0000000..fd4c00e --- /dev/null +++ b/src/main/resources/migrations/user/user-schema-3.yml @@ -0,0 +1,8 @@ +databaseChangeLog: + - changeSet: + id: 2022-05-01-schema-3 + author: user + changes: + - addNotNullConstraint: + tableName: users + columnName: role diff --git a/src/main/resources/public/css/style.css b/src/main/resources/public/css/style.css new file mode 100644 index 0000000..84e36c4 --- /dev/null +++ b/src/main/resources/public/css/style.css @@ -0,0 +1,32 @@ +.container-padding { + padding: 10px; +} + +.margin-bottom { + margin-bottom: 10px; +} + +.button-fixed { + min-width: 140px; +} + +.button-sm { + padding: 1px; +} + +.pagination { + display: inline-block; +} + +.pagination a { + color: black; + float: left; + padding: 5px 5px; + text-decoration: none; +} + +.pagination a.active { + background-color: gray; + color: white; + border-radius: 2px; +} \ No newline at end of file diff --git a/src/main/resources/public/favicon.svg b/src/main/resources/public/favicon.svg new file mode 100644 index 0000000..c2e8ab2 --- /dev/null +++ b/src/main/resources/public/favicon.svg @@ -0,0 +1,4 @@ + + + \ 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..4f93488 --- /dev/null +++ b/src/main/resources/templates/default.html @@ -0,0 +1,63 @@ + + + + + IP Example + + + + + + + + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 0000000..9d66499 --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000..6e14ec0 --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,11 @@ + + + +
+
It's works!
+ ERROR +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..5c9f583 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,30 @@ + + + +
+
+ Пользователь не найден или пароль указан не верно +
+
+ Выход успешно произведен +
+
+ Пользователь '' успешно создан +
+
+
+ +
+
+ +
+ + Регистрация +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/rest-test.html b/src/main/resources/templates/rest-test.html new file mode 100644 index 0000000..6dc9e1e --- /dev/null +++ b/src/main/resources/templates/rest-test.html @@ -0,0 +1,47 @@ + + + + + + +
+
+ + + + + + + + + + +
#IDИмя
+
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html new file mode 100644 index 0000000..8cd75f5 --- /dev/null +++ b/src/main/resources/templates/signup.html @@ -0,0 +1,28 @@ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+ + Назад +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/speaker.html b/src/main/resources/templates/speaker.html new file mode 100644 index 0000000..54f54b1 --- /dev/null +++ b/src/main/resources/templates/speaker.html @@ -0,0 +1,25 @@ + + + +
+

+
+
+ + +
+
+ + +
+ +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/student-edit.html b/src/main/resources/templates/student-edit.html new file mode 100644 index 0000000..21ec00d --- /dev/null +++ b/src/main/resources/templates/student-edit.html @@ -0,0 +1,29 @@ + + + +
+
+
+
+ + +
+
+ + +
+
+ + + Назад + +
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/student.html b/src/main/resources/templates/student.html new file mode 100644 index 0000000..820d306 --- /dev/null +++ b/src/main/resources/templates/student.html @@ -0,0 +1,50 @@ + + + +
+ +
+ + + + + + + + + + + + + + + + + +
#IDИмя
+
+ + Изменить + + +
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/test-result.html b/src/main/resources/templates/test-result.html new file mode 100644 index 0000000..ec6ce0d --- /dev/null +++ b/src/main/resources/templates/test-result.html @@ -0,0 +1,24 @@ + + + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/test.html b/src/main/resources/templates/test.html new file mode 100644 index 0000000..5a97965 --- /dev/null +++ b/src/main/resources/templates/test.html @@ -0,0 +1,21 @@ + + + +
+
+
+
+ + +
+
+ + +
+ +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/users.html b/src/main/resources/templates/users.html new file mode 100644 index 0000000..8b5d13f --- /dev/null +++ b/src/main/resources/templates/users.html @@ -0,0 +1,36 @@ + + + +
+
+ + + + + + + + + + + + + + + + + +
#IDЛогинРоль
+
+ +
+ + \ No newline at end of file