diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml
new file mode 100644
index 0000000..9330a2f
--- /dev/null
+++ b/.github/workflows/pullrequest.yml
@@ -0,0 +1,16 @@
+name: Java CI
+
+on: [push]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up JDK 11
+ uses: actions/setup-java@v1
+ with:
+ java-version: 11
+ - name: Build with Maven
+ run: mvn -B package --file pom.xml
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..4f94986
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client-web/.gitignore b/client-web/.gitignore
new file mode 100644
index 0000000..6b6890a
--- /dev/null
+++ b/client-web/.gitignore
@@ -0,0 +1,23 @@
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+package-lock.json
+
+# testing
+/coverage
+
+# production
+/build
+/node
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/client-web/package.json b/client-web/package.json
new file mode 100644
index 0000000..38ade89
--- /dev/null
+++ b/client-web/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "front-end",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@material-ui/core": "^4.11.0",
+ "@testing-library/jest-dom": "^5.11.5",
+ "@testing-library/react": "^11.1.2",
+ "@testing-library/user-event": "^12.2.2",
+ "bootstrap": "^4.5.3",
+ "node-sass": "^5.0.0",
+ "react": "^17.0.1",
+ "react-dom": "^17.0.1",
+ "react-redux": "^7.2.2",
+ "react-router-dom": "^5.2.0",
+ "react-scripts": "4.0.0",
+ "redux": "^4.0.5",
+ "web-vitals": "^0.2.4"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject",
+ "lint": "tslint src/*",
+ "build:tslint": "npm run lint && npm run build"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version"
+ ]
+ },
+ "devDependencies": {
+ "tslint": "^6.1.3",
+ "typescript": "~3.7.2"
+ }
+}
diff --git a/client-web/public/index.html b/client-web/public/index.html
new file mode 100644
index 0000000..7ceb623
--- /dev/null
+++ b/client-web/public/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ social-network
+
+
+
+
+
+
diff --git a/client-web/src/App.js b/client-web/src/App.js
new file mode 100644
index 0000000..401ee13
--- /dev/null
+++ b/client-web/src/App.js
@@ -0,0 +1,10 @@
+import React from "react"
+import {Login} from "./pages/Login/Login";
+
+function App() {
+ return (
+
+ );
+}
+
+export default App;
diff --git a/client-web/src/index.js b/client-web/src/index.js
new file mode 100644
index 0000000..7384bc7
--- /dev/null
+++ b/client-web/src/index.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root')
+);
diff --git a/client-web/src/pages/Login/Login.tsx b/client-web/src/pages/Login/Login.tsx
new file mode 100644
index 0000000..ec29278
--- /dev/null
+++ b/client-web/src/pages/Login/Login.tsx
@@ -0,0 +1,4 @@
+import * as React from 'react';
+
+export const Login = () => Login page
+
diff --git a/client-web/src/react-app-env.d.ts b/client-web/src/react-app-env.d.ts
new file mode 100644
index 0000000..6431bc5
--- /dev/null
+++ b/client-web/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/client-web/tsconfig.json b/client-web/tsconfig.json
new file mode 100644
index 0000000..7b1d3c6
--- /dev/null
+++ b/client-web/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react"
+ },
+ "include": [
+ "src"
+ ]
+}
diff --git a/client-web/tslint.json b/client-web/tslint.json
new file mode 100644
index 0000000..848319f
--- /dev/null
+++ b/client-web/tslint.json
@@ -0,0 +1,82 @@
+{
+ "extends": "tslint:recommended",
+ "rules": {
+ "max-line-length": {
+ "options": [
+ 120
+ ]
+ },
+ "new-parens": true,
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-conditional-assignment": true,
+ "no-consecutive-blank-lines": false,
+ "no-console": {
+ "severity": "warning",
+ "options": [
+ "debug",
+ "info",
+ "log",
+ "time",
+ "timeEnd",
+ "trace"
+ ]
+ }
+ },
+ "jsRules": {
+ "max-line-length": {
+ "options": [
+ 120
+ ]
+ },
+ "no-empty": true,
+ "member-ordering": [
+ true,
+ {
+ "order": "fields-first"
+ }
+ ],
+ "no-magic-numbers": [
+ true,
+ 1,
+ 2,
+ 3,
+ 0
+ ],
+ "no-reference": true,
+ "ban-comma-operator": true,
+ "curly": [
+ true,
+ "ignore-same-line"
+ ],
+ "no-console": [
+ true,
+ "log",
+ "error"
+ ],
+ "no-duplicate-super": true,
+ "no-duplicate-switch-case": true,
+ "no-duplicate-variable": [
+ true,
+ "check-parameters"
+ ],
+ "no-invalid-template-strings": true,
+ "switch-default": true,
+ "triple-equals": true,
+ "use-isnan": true,
+ "no-duplicate-imports": [
+ true,
+ {
+ "allow-namespace-imports": true
+ }
+ ],
+ "arrow-return-shorthand": true,
+ "ordered-imports": true,
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-operator",
+ "check-typecast"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index c3a79a4..c3c96e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,6 +17,10 @@
11
11
+ UTF-8
+ 3.8.0
+ 3.1.1
+ 3.0.0-M5
@@ -24,6 +28,153 @@
org.springframework.boot
spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ io.jsonwebtoken
+ jjwt
+ 0.9.1
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.0
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.11.3
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.2.1
+
+
+ org.projectlombok
+ lombok
+ 1.18.16
+ provided
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.7.0
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 3.6.0
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.18.1
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${compiler.plugin.version}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${checkstyle.plugin.version}
+
+ checkstyle.xml
+ UTF-8
+ true
+ true
+ false
+
+
+
+ validate
+ validate
+
+ checkstyle
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${surefire.plugin.version}
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ 1.10.3
+
+ client-web
+
+
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+ v14.15.0
+ 6.14.8
+
+
+
+
+ npm install
+
+ npm
+
+
+ install
+
+
+
+
+ npm run build:tslint
+
+ npm
+
+
+ run build:tslint
+
+
+
+
+
+
+
diff --git a/src/main/java/com/kpi/project/App.java b/src/main/java/com/kpi/project/App.java
index 2981ed9..949e52d 100644
--- a/src/main/java/com/kpi/project/App.java
+++ b/src/main/java/com/kpi/project/App.java
@@ -3,8 +3,10 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+@SuppressWarnings("checkstyle:hideutilityclassconstructor")
@SpringBootApplication
public class App {
+
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
diff --git a/src/main/java/com/kpi/project/config/JacksonConfiguration.java b/src/main/java/com/kpi/project/config/JacksonConfiguration.java
new file mode 100644
index 0000000..9ac35ec
--- /dev/null
+++ b/src/main/java/com/kpi/project/config/JacksonConfiguration.java
@@ -0,0 +1,17 @@
+package com.kpi.project.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+
+@Configuration
+public class JacksonConfiguration {
+
+ @Bean
+ public Jackson2ObjectMapperBuilder objectMapperBuilder() {
+ Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
+ builder.serializationInclusion(JsonInclude.Include.NON_NULL);
+ return builder;
+ }
+}
diff --git a/src/main/java/com/kpi/project/config/SecurityConfiguration.java b/src/main/java/com/kpi/project/config/SecurityConfiguration.java
new file mode 100644
index 0000000..a0bb182
--- /dev/null
+++ b/src/main/java/com/kpi/project/config/SecurityConfiguration.java
@@ -0,0 +1,68 @@
+package com.kpi.project.config;
+
+import com.kpi.project.filter.JwtRequestFilter;
+import com.kpi.project.service.UserService;
+import com.kpi.project.util.model.JwtProperties;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@EnableWebSecurity
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ private final UserService userService;
+ private final JwtRequestFilter jwtRequestFilter;
+
+ public SecurityConfiguration(@Lazy UserService userService, @Lazy JwtRequestFilter jwtRequestFilter) {
+ this.userService = userService;
+ this.jwtRequestFilter = jwtRequestFilter;
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.userDetailsService(userService);
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ final String[] testEndpoints = {"test/string", "/test/error", "/test/error2"};
+
+ http.csrf().disable()
+ .authorizeRequests()
+ .antMatchers(
+ ArrayUtils.addAll(testEndpoints,
+ "/authenticate", "/user/registration"))
+ .permitAll()
+ .anyRequest().authenticated()
+ .and().sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+ http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
+ }
+
+ @Override
+ @Bean
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+
+ @Bean
+ @ConfigurationProperties(prefix = "security")
+ public JwtProperties jwtProperties() {
+ return new JwtProperties();
+ }
+
+ @Bean
+ public PasswordEncoder getPasswordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
diff --git a/src/main/java/com/kpi/project/filter/JwtRequestFilter.java b/src/main/java/com/kpi/project/filter/JwtRequestFilter.java
new file mode 100644
index 0000000..be7e44e
--- /dev/null
+++ b/src/main/java/com/kpi/project/filter/JwtRequestFilter.java
@@ -0,0 +1,54 @@
+package com.kpi.project.filter;
+
+import com.kpi.project.service.UserService;
+import com.kpi.project.util.JwtUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class JwtRequestFilter extends OncePerRequestFilter {
+
+ private final JwtUtil jwtUtil;
+ private final UserService userService;
+
+ public JwtRequestFilter(JwtUtil jwtUtil, UserService userService) {
+ this.jwtUtil = jwtUtil;
+ this.userService = userService;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest httpServletRequest,
+ HttpServletResponse httpServletResponse, FilterChain filterChain)
+ throws ServletException, IOException {
+ final String authorizationHeader = httpServletRequest.getHeader("Authorization");
+ String username = null;
+ String token = null;
+ if (StringUtils.isNotBlank(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
+ token = authorizationHeader.substring(7);
+ username = jwtUtil.extractUsername(token);
+ }
+ if (StringUtils.isNotBlank(username) && SecurityContextHolder.getContext().getAuthentication() == null) {
+ final UserDetails userDetails = userService.loadUserByUsername(username);
+ if (jwtUtil.validateToken(token, userDetails)) {
+ final UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
+ new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+ usernamePasswordAuthenticationToken
+ .setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
+ SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
+
+ }
+ }
+ filterChain.doFilter(httpServletRequest, httpServletResponse);
+ }
+}
diff --git a/src/main/java/com/kpi/project/model/ErrorResponse.java b/src/main/java/com/kpi/project/model/ErrorResponse.java
new file mode 100644
index 0000000..de1d92f
--- /dev/null
+++ b/src/main/java/com/kpi/project/model/ErrorResponse.java
@@ -0,0 +1,11 @@
+package com.kpi.project.model;
+
+import com.kpi.project.model.enums.ErrorTypes;
+import lombok.Data;
+
+@Data
+public class ErrorResponse {
+
+ ErrorTypes errorType;
+ String message;
+}
diff --git a/src/main/java/com/kpi/project/model/User.java b/src/main/java/com/kpi/project/model/User.java
new file mode 100644
index 0000000..ac7ce51
--- /dev/null
+++ b/src/main/java/com/kpi/project/model/User.java
@@ -0,0 +1,76 @@
+package com.kpi.project.model;
+
+import com.kpi.project.model.enums.Role;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Table;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "USERS")
+public class User implements UserDetails {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "USER_ID")
+ private Long id;
+
+ @Column(name = "EMAIL", unique = true, nullable = false)
+ private String email;
+
+ @Column(name = "USERNAME", unique = true, nullable = false)
+ private String username;
+
+ @Column(name = "PASSWORD", nullable = false)
+ private String password;
+
+ @ElementCollection(fetch = FetchType.EAGER)
+ @CollectionTable(name = "USER_ROLES", joinColumns = @JoinColumn(name = "USER_ID"))
+ @Enumerated(EnumType.STRING)
+ private Set roles;
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/kpi/project/model/authentication/AuthenticationRequest.java b/src/main/java/com/kpi/project/model/authentication/AuthenticationRequest.java
new file mode 100644
index 0000000..2f12d79
--- /dev/null
+++ b/src/main/java/com/kpi/project/model/authentication/AuthenticationRequest.java
@@ -0,0 +1,15 @@
+package com.kpi.project.model.authentication;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthenticationRequest {
+
+ private String username;
+ private String password;
+
+}
diff --git a/src/main/java/com/kpi/project/model/authentication/AuthenticationResponse.java b/src/main/java/com/kpi/project/model/authentication/AuthenticationResponse.java
new file mode 100644
index 0000000..4785504
--- /dev/null
+++ b/src/main/java/com/kpi/project/model/authentication/AuthenticationResponse.java
@@ -0,0 +1,13 @@
+package com.kpi.project.model.authentication;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthenticationResponse {
+
+ private String token;
+}
diff --git a/src/main/java/com/kpi/project/model/dto/UserDto.java b/src/main/java/com/kpi/project/model/dto/UserDto.java
new file mode 100644
index 0000000..86c0180
--- /dev/null
+++ b/src/main/java/com/kpi/project/model/dto/UserDto.java
@@ -0,0 +1,24 @@
+package com.kpi.project.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.Set;
+
+@Data
+public class UserDto {
+
+ private Long id;
+
+ private String username;
+
+ private String email;
+
+ @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+ private String password;
+
+ private String matchingPassword;
+
+ private Set roles;
+
+}
diff --git a/src/main/java/com/kpi/project/model/enums/ErrorTypes.java b/src/main/java/com/kpi/project/model/enums/ErrorTypes.java
new file mode 100644
index 0000000..40d7d18
--- /dev/null
+++ b/src/main/java/com/kpi/project/model/enums/ErrorTypes.java
@@ -0,0 +1,7 @@
+package com.kpi.project.model.enums;
+
+public enum ErrorTypes {
+
+ server_error,
+ validation_error
+}
diff --git a/src/main/java/com/kpi/project/model/enums/Role.java b/src/main/java/com/kpi/project/model/enums/Role.java
new file mode 100644
index 0000000..1bee412
--- /dev/null
+++ b/src/main/java/com/kpi/project/model/enums/Role.java
@@ -0,0 +1,26 @@
+package com.kpi.project.model.enums;
+
+import org.springframework.security.core.GrantedAuthority;
+
+@SuppressWarnings("checkstyle:hideutilityclassconstructor")
+public enum Role implements GrantedAuthority {
+
+ ADMIN(Code.ADMIN),
+ USER(Code.USER);
+
+ private final String authority;
+
+ Role(String authority) {
+ this.authority = authority;
+ }
+
+ @Override
+ public String getAuthority() {
+ return authority;
+ }
+
+ public class Code {
+ public static final String ADMIN = "ROLE_ADMIN";
+ public static final String USER = "ROLE_USER";
+ }
+}
diff --git a/src/main/java/com/kpi/project/model/exception/ValidatorException.java b/src/main/java/com/kpi/project/model/exception/ValidatorException.java
new file mode 100644
index 0000000..ff83bb1
--- /dev/null
+++ b/src/main/java/com/kpi/project/model/exception/ValidatorException.java
@@ -0,0 +1,12 @@
+package com.kpi.project.model.exception;
+
+public class ValidatorException extends IllegalArgumentException {
+
+ public ValidatorException(String errorMessage) {
+ super(errorMessage);
+ }
+
+ public ValidatorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/com/kpi/project/model/mapper/UserMapper.java b/src/main/java/com/kpi/project/model/mapper/UserMapper.java
new file mode 100644
index 0000000..830b6a4
--- /dev/null
+++ b/src/main/java/com/kpi/project/model/mapper/UserMapper.java
@@ -0,0 +1,35 @@
+package com.kpi.project.model.mapper;
+
+import com.kpi.project.model.User;
+import com.kpi.project.model.dto.UserDto;
+import org.springframework.stereotype.Component;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Component
+public class UserMapper {
+
+ public User dtoToUser(UserDto userDto) {
+ final User user = new User();
+ user.setEmail(userDto.getEmail());
+ user.setPassword(userDto.getPassword());
+ user.setUsername(userDto.getUsername());
+
+ return user;
+ }
+
+ public UserDto userToDto(User user) {
+ final UserDto userDto = new UserDto();
+ userDto.setEmail(user.getEmail());
+ userDto.setPassword(user.getPassword());
+ userDto.setUsername(user.getUsername());
+
+ final Set newRoles = user.getRoles().stream()
+ .map(Enum::toString)
+ .collect(Collectors.toSet());
+ userDto.setRoles(newRoles);
+
+ return userDto;
+ }
+}
diff --git a/src/main/java/com/kpi/project/repository/UserRepository.java b/src/main/java/com/kpi/project/repository/UserRepository.java
new file mode 100644
index 0000000..f027b11
--- /dev/null
+++ b/src/main/java/com/kpi/project/repository/UserRepository.java
@@ -0,0 +1,23 @@
+package com.kpi.project.repository;
+
+import com.kpi.project.model.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserRepository extends JpaRepository {
+
+ @Query("SELECT u FROM User u WHERE u.email = :loginParam or u.username = :loginParam")
+ User loadByEmailOrUsername(@Param("loginParam") String loginParam);
+
+ User findByEmailOrUsername(String email, String username);
+
+ User findByEmail(String email);
+
+ User findByUsername(String username);
+
+ @Query("SELECT u FROM User u WHERE u.id = :idParam")
+ User findByIdIdentifier(@Param("idParam") Long id);
+}
diff --git a/src/main/java/com/kpi/project/resource/ErrorsResource.java b/src/main/java/com/kpi/project/resource/ErrorsResource.java
new file mode 100644
index 0000000..8945a54
--- /dev/null
+++ b/src/main/java/com/kpi/project/resource/ErrorsResource.java
@@ -0,0 +1,36 @@
+package com.kpi.project.resource;
+
+import com.kpi.project.model.ErrorResponse;
+import com.kpi.project.model.enums.ErrorTypes;
+import com.kpi.project.model.exception.ValidatorException;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+@ControllerAdvice
+public class ErrorsResource extends ResponseEntityExceptionHandler {
+
+ @ExceptionHandler(value = {ValidatorException.class})
+ protected ResponseEntity