Human Resources Management System with Spring Boot 3, Spring Security, Spring Data JPA, and PL/SQL Oracle Procedures

by Starling Diaz

In this comprehensive tutorial, we will build a Human Resources Management System using Spring Boot 3, Spring Security, Spring Data JPA, and PL/SQL Oracle Procedures to handle operations like registering and updating users, employees, and admins. We’ll cover step-by-step instructions on setting up the project, configuring security, and making database calls using Oracle stored procedures with the @Procedure annotation.

Table of Contents for This Tutorial

  1.  Introduction to Spring Boot and its advantages.
  2.  Introduction to REST API and DTO Pattern.
  3. Create our project using Spring Initializer: Spring Initializer.
  4. Add dependencies in the POM file such as MapStruct and JWT.
  5. Create our package structure.
  6. Configure application.yml with our database connection and other important properties.
  7. Create our entity class.
  8. Create our DTO class.
  9. Create our mapper class.
  10. Create our security class with the necessary beans.
  11. Create our configuration class with the necessary beans.
  12. Add our beans to the Spring Boot application main class.
  13. Create our Docker file with the configuration to deploy an image and serve mail.
  14. Create our service and implementation classes.
  15. Create our repository class.
  16. Create our exception class.
  17. Create our controller class.
  18. Create our constants.
  19. Create our email HTML template in resources.

1. Introduction to Spring Boot and its Advantages

Spring Boot simplifies Java development by removing boilerplate configuration and providing a rapid way to create production-ready applications. By using Spring Boot, we benefit from features like:

  • Auto-configuration
  • Embedded server support (Tomcat, Jetty)
  • Easy integration with databases (Spring Data JPA)
  • Simplified security (Spring Security)

2. Introduction to REST API and DTO Pattern

  • REST API is an architectural style for building APIs that follow the principles of REST (Representational State Transfer). RESTful APIs provide a way for client-server communication using HTTP methods.
  • DTO Pattern separates the data that the client interacts with from the internal structure of the system, improving flexibility and security.

3. Setting Up the Project with Spring Initializer

Go to Spring Initializer, and create a new Spring Boot project with the following dependencies:

  • Spring Web
  • Spring Data JPA
  • Spring Security
  • Lombok
  • Oracle JDBC
  • JWT
  • Java Mail Sender
  • Thymeleaf
  • Validation

Generate the project and extract it to your workspace.

4. Adding Dependencies

In your pom.xml, add the following dependencies:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.starlingdiaz</groupId>
    <artifactId>human-resources-management</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>human-resources-management</name>
    <description>human-resources-management</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
    </properties>
    <dependencies>

       <!--Spring Boot Starter Data JPA-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--Spring Boot Starter Mail-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <!--Spring Security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--JWT-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--Validation-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</artifactId>
        </dependency>

        <!--Map Struct-->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <!--Springdoc OpenAPI-->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc11</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>17</source>
                        <target>17</target>
                        <annotationProcessorPaths>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                                <version>1.18.20</version>
                            </path>
                            <path>
                                <groupId>org.mapstruct</groupId>
                                <artifactId>mapstruct-processor</artifactId>
                                <version>${org.mapstruct.version}</version>
                            </path>
                        </annotationProcessorPaths>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>

5. Creating the Package Structure

Create the following package structure:

6. Configuring application.yml

Inside the src/main/resources/application.yml, configure your Oracle database connection and security properties:

spring:
  application:
    name: Human Resources Management API
  datasource:
      url: jdbc:oracle:thin:@localhost:1521:xe
      username: C##HUMAN_RESOURCE
      password: 1234
      driver-class-name: oracle.jdbc.OracleDriver

  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        dialect: org.hibernate.dialect.OracleDialect
        format_sql: true

  mail:
      host: localhost
      port: 1025
      username: admin
      password: admin
      properties:
        mail:
          smtp:
            trust: "*"
          auth: true
          starttls:
            enable: true
          connection:
            timeout: 5000
            writetimeout: 5000

security:
  jwt:
    secret-key: "RSjUBwnNFcjYzUFqFFDw1pCFbfZed5MC2QQVzs+CWeY="
    expiration-time: 86400000

mailing:
  frontend:
    activation:
      activationUrl:  http://localhost:4200/activate-account

logging:
  level:
    org:
      springframework:
        security: DEBUG
        web: DEBUG

server:
  port: 8080

7. Creating the Entity Class

We will create the User, Employee, and Admin entities. These classes will map to the corresponding database tables.

User

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.entity;

import jakarta.persistence.*;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.security.Principal;
import java.util.Collection;
import java.util.HashSet;

@Entity
@Table(name = "USERS")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails, Principal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String lastName;
    private String dni;
    private String profession;
    private String address;
    private String country;
    private String email;
    private String Token;

    @Column(nullable = false)
    private String password;
    private boolean enabled = false;

    public Collection<? extends GrantedAuthority> getAuthorities() {
        return new HashSet<>();
    }

    @Override
    public String getUsername() {
        return email;
    }


    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return enabled;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public String FullName() {
        return name + " " + lastName;
    }
}

Employee

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.entity;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "EMPLOYEES")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String position;
    private String department;
    private double salary;
}

Admin

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.entity;
import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "ADMINS")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Admin {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String role;
    private String email;
}

8. Creating the DTO Class

We’ll create DTOs for each entity to handle data transfer between the client and server.

UserDTO.java

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.dto;

import lombok.Data;

@Data
public class UserDTO {
    private String name;
    private String lastName;
    private String dni;
    private String profession;
    private String address;
    private String country;
    private String email;
    private String password;
    private String confirmPassword;
}

AdminDTO.java

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.dto;

import lombok.Data;

@Data
public class AdminDTO {
    private String name;
    private String role;
    private String email;
}

EmployeeDTO.java

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.dto;

import lombok.Data;

@Data
public class EmployeeDTO {

    private String name;
    private String position;
    private String department;
    private double salary;
}

9. Creating the Mapper Class

We will use MapStruct to map between entities and DTOs.

UserMapper.java

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.mapper;

import com.starlingdiaz.humanresourcesmanagement.dto.UserDTO;
import com.starlingdiaz.humanresourcesmanagement.entity.User;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface UserMapper {
    UserDTO toDTO(User user);
    User toEntity(UserDTO userDTO);
}

AdminMapper

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.mapper;

import com.starlingdiaz.humanresourcesmanagement.dto.AdminDTO;
import com.starlingdiaz.humanresourcesmanagement.entity.Admin;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface AdminMapper {
    AdminDTO toDTO(Admin admin);
    Admin toEntity(AdminDTO adminDTO);
}

EmployeeMapper

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.mapper;

import com.starlingdiaz.humanresourcesmanagement.dto.EmployeeDTO;
import com.starlingdiaz.humanresourcesmanagement.entity.Employee;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface EmployeeMapper {
    EmployeeDTO toDTO(Employee employee);
    Employee toEntity(EmployeeDTO employeeDTO);
}

10. Create our security class with the necessary beans.

You need to implement a JWT-based authentication. First, create a custom UserDetailsServiceImpl that loads user details from the database and a JWT token provider to generate tokens into package Security.

first, we need to create a config with beans

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;
import java.util.Collections;

import static org.springframework.http.HttpHeaders.*;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;

@Configuration
@RequiredArgsConstructor
public class BeanConfig {

    private final UserDetailsService userDetailsService;


    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration AuthConfig) throws Exception {
        return AuthConfig.getAuthenticationManager();
    }

    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
        config.setAllowedHeaders(Arrays.asList(
                ORIGIN,
                CONTENT_TYPE,
                ACCEPT,
                AUTHORIZATION
        ));
        config.setAllowedMethods(Arrays.asList(
                "GET",
                "POST",
                "PUT",
                "DELETE"
        ));
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

11. Create our configuration class with the necessary beans.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig{

    private final JwFilterServiceSecurity jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;

    public static String[] PUBLIC_URLS = {
            "/v1/api/**" ,
            "/v2/api-docs" ,
            "/v3/api-docs/**" ,
            "/v3/api-docs/swagger-config",
            "/swagger-resources/**",
            "/swagger-ui.html",
            "/h2-console/login.do",
            "/h2-console/**",
            "/swagger-ui/**",
            "/swagger-ui/index.html",
            "/api/v1/users/register",
            "/api/v1/users/login",
            "/api/v1/**",
            "/api/auth/**",
            "/api/v1/users/activate-account",
    };

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.cors(withDefaults())
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(req ->
                        req.requestMatchers(PUBLIC_URLS)
                                .permitAll().anyRequest().authenticated()
                )
                .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authenticationProvider(authenticationProvider)
                .addFilterBefore((Filter) jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

        http.headers().frameOptions().disable();
        return http.build();
    }
}

Then create files called JwFilterServiceSecurity and JwTServiceSecurity into Package called Security

JwFilterServiceSecurity


@Service
@RequiredArgsConstructor
public class JwFilterServiceSecurity extends OncePerRequestFilter {

    private final JwTServiceSecurity jwTService;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(
            @NotNull HttpServletRequest request,
            @NotNull HttpServletResponse response,
            @NotNull FilterChain filterChain) throws ServletException, IOException {

        if (request.getServletPath().contains("/api/v1/users/login") ||
                request.getServletPath().contains("/api/v1/users/register") ||
                request.getServletPath().contains("/api/v1/users/activate-account") ||
                request.getServletPath().contains("/swagger-ui/") ||
                request.getServletPath().contains("/h2-console/**") ||
                request.getServletPath().contains("/v3/api-docs")) {
            filterChain.doFilter(request, response);
            return;
        }
        final String authorizationHeader = request.getHeader(AUTHORIZATION);
        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            String jwt = authorizationHeader.substring(7);
            String userEmail = jwTService.extractUsername(jwt);

            if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
                if (jwTService.validateToken(jwt, userDetails)) {
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
        }

        filterChain.doFilter(request, response);
    }
}

JwTServiceSecurity

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.security;


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import java.security.Key;
import java.sql.Date;
import java.util.HashMap;
import java.util.function.Function;

@Service
public class JwTServiceSecurity{

    @Value("${security.jwt.expiration-time}")
    private long jwtExpiration;

    @Value("${security.jwt.secret-key}")
    private String secretkey;

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    private <T>  T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    private Key getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secretkey);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    public String generateToken(HashMap<String, Object> claims, UserDetails userDetails){
        return buildToken(claims, userDetails, jwtExpiration);
    }

    private String buildToken(HashMap<String, Object> extraClaims, UserDetails userDetails, long jwtExpiration) {

        var authorities = userDetails.getAuthorities()
                .stream()
                .map(authority -> authority.getAuthority())
                .toList();
        return Jwts.builder()
                .setClaims(extraClaims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
                .claim("authorities", authorities)
                .signWith(getSigningKey())
                .compact();

    }

    public boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));

    }

    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date(System.currentTimeMillis()));
    }


    private Date extractExpiration(String token) {
        return (Date) extractClaim(token, Claims::getExpiration);
    }
    
}
/**
* @author Starling Diaz on 10/20/2024.
* @Github https://github.com/NSTLRD
* @Website https://mentorly.blog/
* @Academy https://www.mentor-ly.com/
* @version human-resources-management 1.0
* @since 10/20/2024.
*/

package com.starlingdiaz.humanresourcesmanagement.service.impl;

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with Email: " + email));
}
}


12. Create our repository class

UserRepository.

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.repository;

import com.starlingdiaz.humanresourcesmanagement.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.query.Procedure;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long>{
    Optional<User> findByEmail(String email);

    // This method links to the 'register_user' stored procedure
    @Procedure(procedureName = "register_user")
    void registerUser(String p_name, String p_lastname, String p_dni, String p_profession, String p_address, String p_country, String p_email, String p_password);

    // This method links to the 'update_user' stored procedure
    @Procedure(procedureName = "update_user")
    void updateUser(Long p_id, String p_name, String p_lastname , String p_dni, String p_profession, String p_address, String p_country);
}

AdminRepository

/**
 * @author Starling Diaz on 10/21/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/21/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.repository;

import com.starlingdiaz.humanresourcesmanagement.entity.Admin;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AdminRepository extends JpaRepository<Admin, Long> {
}

EmployeeRepository

/**
 * @author Starling Diaz on 10/22/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/22/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.repository;

import com.starlingdiaz.humanresourcesmanagement.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

Oracle Procedure

--Register a user

CREATE OR REPLACE PROCEDURE C##HUMAN_RESOURCE.register_user(
    p_name          IN VARCHAR2,
    p_lastname      IN VARCHAR2,
    p_dni           IN VARCHAR2,
    p_profession    IN VARCHAR2,
    p_address       IN VARCHAR2,
    p_country       IN VARCHAR2,
    p_email         IN VARCHAR2,
    p_password      IN VARCHAR2,
    p_token         IN VARCHAR2,-- nuevo parámetro para el token
    p_user_id       OUT NUMBER
)
AS
BEGIN
    INSERT INTO C##HUMAN_RESOURCE.USERS (name, last_name, dni, profession, address, country, email, password, enabled, token)
    VALUES (p_name, p_lastname, p_dni, p_profession, p_address, p_country, p_email, p_password, '0', p_token)
    RETURNING id INTO p_user_id;
END;


--UPDATE A USER
CREATE OR REPLACE PROCEDURE C##HUMAN_RESOURCE.update_user(
    p_id            IN NUMBER,
    p_name          IN VARCHAR2,
    p_lastname      IN VARCHAR2,
    p_dni           IN VARCHAR2,
    p_profession    IN VARCHAR2,
    p_address       IN VARCHAR2,
    p_country       IN VARCHAR2)
AS
BEGIN
    UPDATE C##HUMAN_RESOURCE.USERS
    SET name = p_name,
        last_name = p_lastname,
        dni = p_dni,
        profession = p_profession,
        address = p_address,
        country = p_country
    WHERE id = p_id;
END;
/

--Register Admin
CREATE OR REPLACE PROCEDURE C##HUMAN_RESOURCE.register_admin(
    p_email IN VARCHAR2,
    p_name  IN VARCHAR2,
    p_role  IN VARCHAR2
    
    )
AS
BEGIN
    INSERT INTO C##HUMAN_RESOURCE.ADMINS (email, name, role)
    VALUES (p_email, p_name, p_role);
END;

--UPDATE A ADMIN
CREATE OR REPLACE PROCEDURE C##HUMAN_RESOURCE.update_admin(
    p_id    IN NUMBER,
    p_email IN VARCHAR2,
    p_name  IN VARCHAR2,
    p_role  IN VARCHAR2
    )
AS
BEGIN
    UPDATE C##HUMAN_RESOURCE.ADMINS
    SET email = p_email,
        name =  p_name,
        role =  p_role
    WHERE id = p_id;
END;
/


--Register EMPLOYEES
CREATE OR REPLACE PROCEDURE C##HUMAN_RESOURCE.register_employee(
    p_name  IN VARCHAR2,
    p_position  IN VARCHAR2,
    p_department IN VARCHAR2,
    p_salary    IN NUMBER
)
AS
BEGIN
    INSERT INTO C##HUMAN_RESOURCE.EMPLOYEES (name, position, department, salary)
    VALUES (p_name, p_position, p_department, p_salary);
END;

/
pendiente
--UPDATE A EMPLOYEES
CREATE OR REPLACE PROCEDURE C##HUMAN_RESOURCE.update_employee(
    p_id          IN NUMBER,
    p_name        IN VARCHAR2,
    p_position    IN VARCHAR2,
    p_department  IN VARCHAR2,
    p_salary      IN Number
    )
AS
BEGIN
    UPDATE C##HUMAN_RESOURCE.EMPLOYEES
    SET name = p_name,
        position =  p_position,
        department =  p_department,
        salary = p_salary
    WHERE id = p_id;
END;
/

13. Create our service and implementation classes.

UserService

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.service;

import com.starlingdiaz.humanresourcesmanagement.dto.LoginDTO;
import com.starlingdiaz.humanresourcesmanagement.dto.UserDTO;
import com.starlingdiaz.humanresourcesmanagement.dto.response.LoginResponseDTO;
import com.starlingdiaz.humanresourcesmanagement.entity.User;
import com.starlingdiaz.humanresourcesmanagement.exception.TokenExpiredException;
import jakarta.mail.MessagingException;
import org.springframework.stereotype.Service;

@Service
public interface UserService {
    UserDTO registerUser(UserDTO userDTO) throws Exception;
    void updateUser(Long id, UserDTO userDTO);
    String generateAndSaveActivationToken(User user);
    String generateActivationCode(int length);
    LoginResponseDTO loginAuthenticate(LoginDTO loginDto);
    String activateAccount(String token) throws MessagingException, TokenExpiredException;
}

Next, we need to create the UserServiceImpl class, which implements the UserService interface. In this class, we can use EntityManager to interact with the database in a more flexible way, leveraging helpful methods such as those that allow us to invoke stored procedures. The EntityManager provides a way to handle native queries and stored procedures, which is especially useful when we need to pass specific parameters to these procedures, aligning our business logic with the underlying database operations.

The following code demonstrates how to call the stored procedures register_user and update_user using EntityManager for registering and updating users in the database. We define the necessary input parameters for each procedure and map them to the values from the UserDTO object.

By using StoredProcedureQuery, we are able to define and execute the stored procedure, passing the appropriate arguments to handle the user registration and update processes. Below is the full implementation of the service:

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.service.impl;

import com.starlingdiaz.humanresourcesmanagement.constants.EmailTemplateName;
import com.starlingdiaz.humanresourcesmanagement.dto.LoginDTO;
import com.starlingdiaz.humanresourcesmanagement.dto.UserDTO;
import com.starlingdiaz.humanresourcesmanagement.dto.response.LoginResponseDTO;
import com.starlingdiaz.humanresourcesmanagement.entity.Token;
import com.starlingdiaz.humanresourcesmanagement.entity.User;
import com.starlingdiaz.humanresourcesmanagement.exception.TokenExpiredException;
import com.starlingdiaz.humanresourcesmanagement.exception.UserNotFoundException;
import com.starlingdiaz.humanresourcesmanagement.repository.TokenRepository;
import com.starlingdiaz.humanresourcesmanagement.repository.UserRepository;
import com.starlingdiaz.humanresourcesmanagement.security.JwTServiceSecurity;
import com.starlingdiaz.humanresourcesmanagement.service.UserService;
import jakarta.mail.MessagingException;
import jakarta.persistence.*;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

    public static final String REGISTER_USER = "register_user";
    public static final String UPDATE_USER = "update_user";

    @PersistenceContext
    private EntityManager entityManager;

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final TokenRepository tokenRepository;
    private final EmailServiceImpl emailServiceImpl;
    private final AuthenticationManager authenticationManager;
    private final JwTServiceSecurity jwTServiceSecurity;

    @Value("${mailing.frontend.activation.activationUrl}")
    private String activationUrl;

    @Override
    public UserDTO registerUser(UserDTO userDTO) throws Exception {
        String activationToken = generateActivationCode(6);
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery(REGISTER_USER);

        registerUserParameter(query);
        registerUserSetParameter(userDTO, query, activationToken);
        query.execute();

        // Get ID of User
        BigDecimal userIdBigDecimal = (BigDecimal) query.getOutputParameterValue(10);
        Long userId = userIdBigDecimal.longValue();

        // Search the user created
        User newUser = userRepository.findById(userId)
                .orElseThrow(() -> new EntityNotFoundException("User not found after creation"));

        // save token
        saveTokenForUser(newUser, activationToken);
        sendVerificationEmail(newUser);
        return userDTO;
    }

    private void registerUserSetParameter(UserDTO userDTO, StoredProcedureQuery query, String activationToken) {
        query.setParameter(1, userDTO.getName());
        query.setParameter(2, userDTO.getLastName());
        query.setParameter(3, userDTO.getDni());
        query.setParameter(4, userDTO.getProfession());
        query.setParameter(5, userDTO.getAddress());
        query.setParameter(6, userDTO.getCountry());
        query.setParameter(7, userDTO.getEmail());
        query.setParameter(8, passwordEncoder.encode(userDTO.getPassword()));
        query.setParameter(9, activationToken);
    }

    private static void registerUserParameter(StoredProcedureQuery query) {
        query.registerStoredProcedureParameter(1, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(2, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(3, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(4, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(5, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(6, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(7, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(8, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(9, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(10, BigDecimal.class, ParameterMode.OUT);
    }

    private void saveTokenForUser(User user, String tokenValue) {
        Token token = Token.builder()
                .token(tokenValue)
                .createdAt(LocalDateTime.now())
                .expiredAt(LocalDateTime.now().plusMinutes(45))
                .user(user)
                .build();

        // Guardar el token en la base de datos
        tokenRepository.save(token);
    }

    private void sendVerificationEmail(User user) throws MessagingException {
        emailServiceImpl.sendEmail(
                user.getEmail(),
                user.FullName(),
                EmailTemplateName.ACTIVATE_ACCOUNT,
                activationUrl,
                user.getToken(),
                "Activate your account"
        );
    }

    @Override
    public void updateUser(Long id, UserDTO userDTO){
        // Call the stored procedure using EntityManager
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery(UPDATE_USER);
        query.registerStoredProcedureParameter(1, Long.class,   ParameterMode.IN);
        query.registerStoredProcedureParameter(2, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(3, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(4, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(5, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(6, String.class, ParameterMode.IN);

        query.setParameter(1, id);
        query.setParameter(2, userDTO.getName());
        query.setParameter(3, userDTO.getDni());
        query.setParameter(4, userDTO.getProfession());
        query.setParameter(5, userDTO.getAddress());
        query.setParameter(6, userDTO.getCountry());
        query.execute();

    }


    @Override
    public String generateAndSaveActivationToken(User user) {
        String generatedToken = generateActivationCode(6);
        var token = Token.builder()
                .token(generatedToken)
                .createdAt(LocalDateTime.now())
                .expiredAt(LocalDateTime.now().plusMinutes(45))
                .user(user)
                .build();
        tokenRepository.save(token);
        return generatedToken;
    }

    @Override
    public String generateActivationCode(int length) {
        String characters = "123456789";
        StringBuilder result = new StringBuilder();
        SecureRandom random = new SecureRandom();
        for (int i = 0; i < length; i++) {
            int randomIndex = random.nextInt(characters.length());
            result.append(characters.charAt(randomIndex));
        }
        return result.toString();
    }


    @Override
    public LoginResponseDTO loginAuthenticate(LoginDTO loginDto) {
        if(loginDto.getEmail() == null || loginDto.getEmail().isEmpty()){
            throw new RuntimeException("Email is required");
        }

        if(loginDto.getPassword() == null || loginDto.getPassword().isEmpty()){
            throw new RuntimeException("Password is required");
        }

        try {
            var auth = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginDto.getEmail(), loginDto.getPassword())
            );
            var user = (User) auth.getPrincipal();
            var claims = new HashMap<String, Object>();
            claims.put("name", user.getName());
            claims.put("email", user.getEmail());
            claims.put("id", user.getId());
            var jwtToken = jwTServiceSecurity.generateToken(claims, user);
            return LoginResponseDTO.builder().token(jwtToken).build();
        } catch (AuthenticationException e){
            if(userRepository.findByEmail(loginDto.getEmail()).isPresent()){
                throw new RuntimeException("Password is incorrect");
            } else {
                throw new UserNotFoundException("No user found with the provided email address");
            }
        }
    }

    @Override
    public String activateAccount(String token) throws MessagingException, TokenExpiredException {
        Optional<Token> tokenOptional = Optional.ofNullable(tokenRepository.findByToken(token));
        if(!tokenOptional.isPresent()){
            throw new TokenExpiredException("Invalid Token");
        }

        Token savedToken = tokenOptional.get();
        if(LocalDateTime.now().isAfter(savedToken.getExpiredAt())){
            sendVerificationEmail(savedToken.getUser());
            throw new TokenExpiredException("Activation token has expired. A new token has been sent to the email address.");
        }

        User user = savedToken.getUser();
        if (user == null) {
            throw new UserNotFoundException("User not found");
        }

        user.setEnabled(true);
        userRepository.save(user);

        savedToken.setValidatedAt(LocalDateTime.now());
        tokenRepository.save(savedToken);
        return token;
    }
}

AdminService

/**
 * @author Starling Diaz on 10/21/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/21/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.service;

import com.starlingdiaz.humanresourcesmanagement.dto.AdminDTO;
import com.starlingdiaz.humanresourcesmanagement.dto.UserDTO;

public interface AdminService {
    void registerAdmin(AdminDTO adminDTO);
    void updateAdmin(Long id, UserDTO userDTO);
}

AdminServiceImpl

/**
 * @author Starling Diaz on 10/21/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/21/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.service.impl;

import com.starlingdiaz.humanresourcesmanagement.dto.AdminDTO;
import com.starlingdiaz.humanresourcesmanagement.service.AdminService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.StoredProcedureQuery;
import org.springframework.stereotype.Service;

@Service
public class AdminServiceImpl implements AdminService {

    public static final String REGISTER_ADMIN = "register_admin";
    public static final String UPDATE_ADMIN = "update_admin";
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public void registerAdmin(AdminDTO adminDTO) {
        // Call the stored procedure using EntityManager
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery(REGISTER_ADMIN);
        query.registerStoredProcedureParameter(1, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(2, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(3, String.class, ParameterMode.IN);

        query.setParameter(1, adminDTO.getName());
        query.setParameter(2, adminDTO.getRole());
        query.setParameter(3, adminDTO.getEmail());
        query.execute();

    }

    @Override
    public void updateAdmin(Long id, AdminDTO adminDTO) {
        // Call the stored procedure using EntityManager
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery(UPDATE_ADMIN);
        query.registerStoredProcedureParameter(1, Long.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(2, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(3, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(4, String.class, ParameterMode.IN);

        query.setParameter(1, id);
        query.setParameter(2, adminDTO.getName());
        query.setParameter(3, adminDTO.getRole());
        query.setParameter(4, adminDTO.getEmail());
        query.execute();
    }
}

EmployeeService

/**
 * @author Starling Diaz on 10/22/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/22/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.service;

import com.starlingdiaz.humanresourcesmanagement.dto.EmployeeDTO;

import java.util.List;

public interface EmployeeService {

    void registerEmployee(EmployeeDTO employeeDTO);
    void updateEmployee(Long id, EmployeeDTO employeeDTO);
    void deleteEmployee(Long id);
    List<EmployeeDTO> getAllEmployees();
    EmployeeDTO getEmployeeById(Long id);
}

EmployeeServiceImpl

/**
 * @author Starling Diaz on 10/22/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/22/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.service.impl;

import com.starlingdiaz.humanresourcesmanagement.dto.EmployeeDTO;
import com.starlingdiaz.humanresourcesmanagement.mapper.EmployeeMapper;
import com.starlingdiaz.humanresourcesmanagement.mapper.EmployeeMapperImpl;
import com.starlingdiaz.humanresourcesmanagement.repository.EmployeeRepository;
import com.starlingdiaz.humanresourcesmanagement.service.EmployeeService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.StoredProcedureQuery;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@AllArgsConstructor
public class EmployeeServiceImpl implements EmployeeService {

    public static final String REGISTER_EMPLOYEE = "register_employee";
    public static final String UPDATE_EMPLOYEE = "update_employee";
    private final EmployeeMapper employeeMapper;
    @PersistenceContext
    private EntityManager entityManager;

    private final EmployeeRepository employeeRepository;

    /**
     * Register an employee
     * @param employeeDTO
     * name
     * position
     * department
     * salary
     */
    @Override
    public void registerEmployee(EmployeeDTO employeeDTO) {
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery(REGISTER_EMPLOYEE);
        query.registerStoredProcedureParameter(1, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(2, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(3, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(4, Double.class, ParameterMode.IN);

        query.setParameter(1, employeeDTO.getName());
        query.setParameter(2, employeeDTO.getPosition());
        query.setParameter(3, employeeDTO.getDepartment());
        query.setParameter(4, employeeDTO.getSalary());
        query.execute();
    }

    @Override
    public void updateEmployee(Long id, EmployeeDTO employeeDTO) {
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery(UPDATE_EMPLOYEE);
        query.registerStoredProcedureParameter(1, Long.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(2, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(3, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(4, String.class, ParameterMode.IN);
        query.registerStoredProcedureParameter(5, Double.class, ParameterMode.IN);

        query.setParameter(1, id);
        query.setParameter(2, employeeDTO.getName());
        query.setParameter(3, employeeDTO.getPosition());
        query.setParameter(4, employeeDTO.getDepartment());
        query.setParameter(5, employeeDTO.getSalary());
        query.execute();

    }

    @Override
    public void deleteEmployee(Long id) {
        this.entityManager.createQuery("DELETE FROM Employee e WHERE e.id = :id")
                .setParameter("id", id)
                .executeUpdate();
    }

    @Override
    public List<EmployeeDTO> getAllEmployees() {
      return employeeRepository.findAll()
              .stream().map(employeeMapper::toDTO)
              .collect(Collectors.toList());
    }

    @Override
    public EmployeeDTO getEmployeeById(Long id) {
        return this.entityManager.find(EmployeeDTO.class, id);
    }
}

14. Create our controller class.

UserController

/**
 * @author Starling Diaz on 10/20/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/20/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.controller;

import com.starlingdiaz.humanresourcesmanagement.dto.UserDTO;
import com.starlingdiaz.humanresourcesmanagement.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/register")
    public ResponseEntity<String> registerUser(@RequestBody UserDTO userDTO) {
        userService.registerUser(userDTO);
        return ResponseEntity.ok("User registered successfully.");
    }

    @PutMapping("/update/{id}")
    public ResponseEntity<String> updateUser(@PathVariable Long id, @RequestBody UserDTO userDTO) {
        userService.updateUser(id, userDTO);
        return ResponseEntity.ok("User updated successfully.");
    }
}

AdminController

/**
 * @author Starling Diaz on 10/21/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/21/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.controller;

import com.starlingdiaz.humanresourcesmanagement.dto.AdminDTO;
import com.starlingdiaz.humanresourcesmanagement.dto.UserDTO;
import com.starlingdiaz.humanresourcesmanagement.service.AdminService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/admins")
@RequiredArgsConstructor
public class AdminController {
    
    private final AdminService adminService;

    @PostMapping("/register-admin")
    public ResponseEntity<String> registerUser(@RequestBody AdminDTO adminDTO) {
        adminService.registerAdmin(adminDTO);
        return ResponseEntity.ok("Admin registered successfully.");
    }

    @PutMapping("/update-admin/{id}")
    public ResponseEntity<String> updateUser(@PathVariable Long id, @RequestBody AdminDTO adminDTO) {
        adminService.updateAdmin(id, adminDTO);
        return ResponseEntity.ok("Admin updated successfully.");
    }
}

EmployeeController

/**
 * @author Starling Diaz on 10/22/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/22/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.controller;

import com.starlingdiaz.humanresourcesmanagement.dto.EmployeeDTO;
import com.starlingdiaz.humanresourcesmanagement.service.EmployeeService;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/v1/employees")
@AllArgsConstructor
public class EmployeeController {

    private final EmployeeService employeeService;


    @PostMapping("/register-employee")
    public ResponseEntity<String> registerEmployee(@RequestBody EmployeeDTO employeeDTO) {
        employeeService.registerEmployee(employeeDTO);
        return ResponseEntity.ok("Employee registered successfully.");
    }

    @PutMapping("/update-employee/{id}")
    public ResponseEntity<String> updateEmployee(@PathVariable Long id, @RequestBody EmployeeDTO employeeDTO) {
        employeeService.updateEmployee(id, employeeDTO);
        return ResponseEntity.ok("Employee updated successfully.");
    }

    @DeleteMapping("/delete-employee/{id}")
    public ResponseEntity<String> deleteEmployee(@PathVariable Long id) {
        employeeService.deleteEmployee(id);
        return ResponseEntity.ok("Employee deleted successfully.");
    }

    @GetMapping("/get-all-employees")
    public ResponseEntity<List<EmployeeDTO>> getAllEmployees() {
        return ResponseEntity.ok(employeeService.getAllEmployees());
    }
}

15. Create our Constans and EmailService and Email HTML Template in resources.

EmailTemplateName

/**
 * @author Starling Diaz on 10/22/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/22/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.constants;

import lombok.Getter;

@Getter
public enum EmailTemplateName {

    ACTIVATE_ACCOUNT("activate_account"),
    RESET_PASSWORD("reset-password");

    private final String name;

    EmailTemplateName(String name) {
        this.name = name;
    }
}

EmailService

/**
 * @author Starling Diaz on 10/22/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/22/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.service;

import com.starlingdiaz.humanresourcesmanagement.constants.EmailTemplateName;
import jakarta.mail.MessagingException;

public interface EmailService {

    void sendEmail(String to, String username, EmailTemplateName emailTemplate, String ConfirmationUrl, String activationConde, String subject) throws MessagingException;
}

EmailServiceImpl

/**
 * @author Starling Diaz on 10/22/2024.
 * @Github https://github.com/NSTLRD
 * @Website https://mentorly.blog/
 * @Academy https://www.mentor-ly.com/
 * @version human-resources-management 1.0
 * @since 10/22/2024.
 */

package com.starlingdiaz.humanresourcesmanagement.service.impl;

import com.starlingdiaz.humanresourcesmanagement.constants.EmailTemplateName;
import com.starlingdiaz.humanresourcesmanagement.service.EmailService;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.AllArgsConstructor;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@Service
@AllArgsConstructor
public class EmailServiceImpl implements EmailService {
    private final JavaMailSender javaMailSender;
    private final SpringTemplateEngine templateEngine;


    @Override
    @Async
    public void sendEmail(String to, String username, EmailTemplateName emailTemplate, String ConfirmationUrl, String activationConde, String subject) throws MessagingException {

        // send email
        String templateName = null;
        switch (emailTemplate == null ? EmailTemplateName.ACTIVATE_ACCOUNT : emailTemplate) {
            case ACTIVATE_ACCOUNT:
                templateName = "activate_account";
                break;
            case RESET_PASSWORD:
                templateName = "reset-password";
                break;
        }
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
                MimeMessageHelper.MULTIPART_MODE_MIXED,
                StandardCharsets.UTF_8.name()
        );

        //add the parameters to the template
        Map<String, Object> properties = new HashMap<>();
        properties.put("username", username);
        properties.put("confirmationUrl", ConfirmationUrl);
        properties.put("activation_code", activationConde);

        //pass the parameters to the template
        Context context = new Context();
        context.setVariables(properties);

        //create the email body
        helper.setFrom("[email protected]");
        helper.setTo(to);
        helper.setSubject(subject);

        String template = templateEngine.process(templateName, context);
        helper.setText(template, true);
        javaMailSender.send(mimeMessage);
    }
}

Conclusion

Thank you for following along with the development of the Human Resources Management project! We hope that this guide has been helpful in understanding the process of building a Spring Boot REST API with best practices. Feel free to explore the full project on GitHub and stay connected through our various platforms to continue learning and improving your skills.

For more projects, tutorials, and resources, check out the links below:

Contact and Social Links

Feel free to contribute to the repository, share your feedback, or reach out if you have any questions. We’re looking forward to connecting with you across our platforms. Happy coding!

Related Posts

About Us

We are Mentorly, your space for learning and practicing with real industry projects that will allow you to develop problem-solving skills with the best practices. Offering mentorships and real-world challenges to push you and support you in your professional development.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More