Spring Boot 3

ApiAuthenticationEntryPoint.java

package com.slsb.expense.tracker.jwtAuth.config;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;

@Component
public class ApiAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

ApplicationConfig.java

package com.slsb.expense.tracker.jwtAuth.config;

import com.slsb.expense.tracker.jwtAuth.user.UserRepository;
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.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableMethodSecurity
@RequiredArgsConstructor
public class ApplicationConfig {

    private final UserRepository userRepository;

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> userRepository.findByEmail(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
    }

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

        return authProvider;
    }

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

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

JwtAuthenticationFilter.java

package com.slsb.expense.tracker.jwtAuth.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.antlr.v4.runtime.misc.NotNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;

@Component
@EnableWebSecurity
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

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

        final String authHeader = request.getHeader("Authorization");
        final String jwt;
        final String userEmail;

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        jwt = authHeader.split(" ")[1].trim();
        userEmail = jwtService.extractUsername(jwt);

        if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);

            if (jwtService.isTokenValid(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                        userDetails.getUsername(),
                        null,
                        userDetails.getAuthorities()
                );
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }

        filterChain.doFilter(request, response);
    }
}

JwtService.java

package com.slsb.expense.tracker.jwtAuth.config;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtService {

    private static final long tokenExpirationTime = (1000 * 60 * 30);
    private static final String secretKey = "dAJEVMAFChvUGIjRf3Rg5NiR2MjQpM+OYUeU1iqkxEopP137ZGNjVPSGp6y04WtT";
    private static final Key secretKeyAuto = Keys.secretKeyFor(SignatureAlgorithm.HS256);

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

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

    public String generateToken(UserDetails userDetails) {
        return generateToken(new HashMap<>(), userDetails);
    }

    public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
        return Jwts.builder()
                .setClaims(extraClaims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpirationTime))
                .signWith(getSignInKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    public boolean isTokenValid(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());
    }

    private Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

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

    private Key getSignInKey() {
        return secretKeyAuto;
    }
}

SecurityConfiguration.java

package com.slsb.expense.tracker.jwtAuth.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.function.Consumer;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final AuthenticationProvider authenticationProvider;
    private final JwtAuthenticationFilter jwtAuthFilter;
    private final ApiAuthenticationEntryPoint apiAuthenticationEntryPoint;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.csrf(AbstractHttpConfigurer::disable)
                .cors(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(configurer -> configurer
                        .requestMatchers("/api/auth/**").permitAll()
                        .anyRequest().authenticated())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authenticationProvider(authenticationProvider)
                .exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(apiAuthenticationEntryPoint))
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }
}

User.java

package com.slsb.expense.tracker.jwtAuth.user;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Date;
import java.util.List;

@Entity
@Getter
@Setter
@ToString
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user_master")
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id", unique = true, nullable = false)
    private Long userId;

    @Column(name = "name", nullable = false)
    @NotBlank(message = "Name is mandatory")
    private String name;

    @Column(name = "email", unique = true, nullable = false)
    @NotBlank(message = "Email is mandatory")
    private String email;

    @Column(name = "password", nullable = false)
    @NotBlank(message = "Password is mandatory")
    private String password;

    @DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss")
    @Column(name = "birth_date")
    private Date birthdate;

    @Enumerated(EnumType.STRING)
    private Role role;

    @Column(name = "active_flag", nullable = false)
    private int activeFlag;

    @DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss")
    @Column(name = "created_date", nullable = false)
    private Date createdDate;

    @Column(name = "created_by_ip", nullable = false)
    private String createdByIp;

    @DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss")
    @Column(name = "updated_date")
    private Date updatedDate;

    @Column(name = "updated_by_ip")
    private String updatedByIp;

    @Override
   

 public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority(role.name()));
    }

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

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

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

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

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

    @Override
    public String getPassword() {
        return password;
    }
}

UserRepository.java

package com.slsb.expense.tracker.jwtAuth.user;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByEmail(String email);
}

Role.java

package com.slsb.expense.tracker.jwtAuth.user;

public enum Role {
    USER,
    ADMIN
}

AuthenticationController.java

package com.slsb.expense.tracker.jwtAuth.auth;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
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;

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthenticationController {

    private final AuthenticationService service;

    @PostMapping("/register")
    public ResponseEntity<AuthenticationResponse> register (
            HttpServletRequest request,
            @Valid @RequestBody RegisterRequest registerRequest
    ) {
        return ResponseEntity.ok(service.register(request, registerRequest));
    }

    @PostMapping("/login")
    public ResponseEntity<AuthenticationResponse> login (
            HttpServletRequest request,
            @Valid @RequestBody AuthenticationRequest loginRequest
    ) {
        return ResponseEntity.ok(service.authenticate(request, loginRequest));
    }

    @PostMapping("/authenticate")
    public ResponseEntity<AuthenticationResponse> authenticate (
            HttpServletRequest request,
            @Valid @RequestBody AuthenticationRequest authenticateRequest
    ) {
        return ResponseEntity.ok(service.authenticate(request, authenticateRequest));
    }
}

AuthenticationService.java

package com.slsb.expense.tracker.jwtAuth.auth;

import com.slsb.expense.tracker.jwtAuth.config.JwtService;
import com.slsb.expense.tracker.jwtAuth.user.Role;
import com.slsb.expense.tracker.jwtAuth.user.User;
import com.slsb.expense.tracker.jwtAuth.user.UserRepository;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Date;

@Service
@RequiredArgsConstructor
public class AuthenticationService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtService jwtService;
    private final AuthenticationManager authenticationManager;

    public AuthenticationResponse register(HttpServletRequest request, RegisterRequest registerRequest) {
        var user = User.builder()
                .name(registerRequest.getName())
                .email(registerRequest.getEmail())
                .password(passwordEncoder.encode(registerRequest.getPassword()))
                .birthdate(registerRequest.getBirthdate())
                .role(Role.USER)
                .createdByIp(request.getRemoteAddr())
                .createdDate(new Date())
                .build();

        userRepository.save(user);

        var jwtToken = jwtService.generateToken(user);
        User registeredUser = userRepository.findByEmail(registerRequest.getEmail()).orElseThrow();

        return AuthenticationResponse.builder()
                .token(jwtToken)
                .email(registeredUser.getEmail())
                .userId(registeredUser.getUserId())
                .build();
    }

    public AuthenticationResponse authenticate(HttpServletRequest request, AuthenticationRequest authenticationRequest) {
        authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        authenticationRequest.getEmail(),
                        authenticationRequest.getPassword()
                )
        );

        var user = userRepository.findByEmail(authenticationRequest.getEmail()).orElseThrow();
        var jwtToken = jwtService.generateToken(user);

        return AuthenticationResponse.builder()
                .token(jwtToken)
                .email(user.getEmail())
                .userId(user.getUserId())
                .build();
    }
}

CashBook.java

package com.slsb.expense.tracker.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.slsb.expense.tracker.jwtAuth.user.Role;
import com.slsb.expense.tracker.jwtAuth.user.User;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;

@Entity
@Getter
@Setter
@ToString
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "cashbook",
        uniqueConstraints = @UniqueConstraint(columnNames={"cashbook_name", "user_id"})
)
public class CashBook extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cashbook_id", nullable = false)
    private Long cashbookId;

    @Column(name = "cashbook_name", nullable = false)
    @NotBlank(message = "Cash Book Name is mandatory")
    private String cashbookName;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
}

Expense.java

package com.slsb.expense.tracker.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.slsb.expense.tracker.jwtAuth.user.Role;
import com.slsb.expense.tracker.util.expenseTrackerEnum.EntryType;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;

@Entity
@Getter
@Setter
@ToString
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "expense",
        uniqueConstraints = @UniqueConstraint(columnNames={"expense_name", "cashbook_id"})
)
public class Expense extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "expense_id", nullable = false)
    private Long expenseId;

    @Column(name = "expense_name", nullable = false)
    @NotBlank(message = "Expense Name is mandatory")
    private String expenseName;

    @Enumerated(EnumType.STRING)
    @NotNull(message = "Entry Type is mandatory.")
    private EntryType entryType;

    @DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss")
    @Column(name = "expense_date_time", nullable = false)
    private Date expenseDateTime;

    @Column(name = "amount", nullable = false)
    @NotNull(message = "Amount is mandatory.")
    private BigDecimal amount;

    @Column(name = "remarks")
    private String remarks;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "category_id", nullable = false)
    private Category category;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "paymentMode_id", nullable = false)
    private PaymentMode paymentMode;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "cashbook_id", nullable = false)
    private CashBook cashBook;
}

Category.java

package com.slsb.expense.tracker.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.slsb.expense.tracker.jwtAuth.user.User;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
import lombok.experimental.SuperBuilder;

@Entity
@Getter
@Setter
@ToString
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "category",
        uniqueConstraints = @UniqueConstraint(columnNames={"category_name", "cashbook_id"})
)
public class Category extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "category_id", nullable = false)
    private Long categoryId;

    @Column(name = "category_name", nullable = false)
    @NotBlank(message = "Category Name is mandatory")
    private String categoryName;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "cashbook_id", nullable = false)
    private CashBook cashBook;
}

CashBook.java

package com.slsb.expense.tracker.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.slsb.expense.tracker.jwtAuth.user.Role;
import com.slsb.expense.tracker.jwtAuth.user.User;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;

@Entity
@Getter
@Setter
@ToString
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "cashbook",
        uniqueConstraints = @UniqueConstraint(columnNames={"cashbook_name", "user_id"})
)
public class CashBook extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cashbook_id", nullable = false)
    private Long cashbookId;

    @Column(name = "cashbook_name", nullable = false)
    @NotBlank(message = "Cash Book Name is mandatory")
    private String cashbookName;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
}

Expense.java

package com.slsb.expense.tracker.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.slsb.expense.tracker.jwtAuth.user.Role;
import com.slsb.expense.tracker.util.expenseTrackerEnum.EntryType;


import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;

@Entity
@Getter
@Setter
@ToString
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "expense",
        uniqueConstraints = @UniqueConstraint(columnNames={"expense_name", "cashbook_id"})
)
public class Expense extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "expense_id", nullable = false)
    private Long expenseId;

    @Column(name = "expense_name", nullable = false)
    @NotBlank(message = "Expense Name is mandatory")
    private String expenseName;

    @Enumerated(EnumType.STRING)
    @NotNull(message = "Entry Type is mandatory.")
    private EntryType entryType;

    @DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss")
    @Column(name = "expense_date_time", nullable = false)
    private Date expenseDateTime;

    @Column(name = "amount", nullable = false)
    @NotNull(message = "Amount is mandatory.")
    private BigDecimal amount;

    @Column(name = "remarks")
    private String remarks;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "category_id", nullable = false)
    private Category category;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "paymentMode_id", nullable = false)
    private PaymentMode paymentMode;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "cashbook_id", nullable = false)
    private CashBook cashBook;
}

Category.java

package com.slsb.expense.tracker.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.slsb.expense.tracker.jwtAuth.user.User;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
import lombok.experimental.SuperBuilder;

@Entity
@Getter
@Setter
@ToString
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "category",
        uniqueConstraints = @UniqueConstraint(columnNames={"category_name", "cashbook_id"})
)
public class Category extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "category_id", nullable = false)
    private Long categoryId;

    @Column(name = "category_name", nullable = false)
    @NotBlank(message = "Category Name is mandatory")
    private String categoryName;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "cashbook_id", nullable = false)
    private CashBook cashBook;
}

Formatted Java Code - Expense Tracker Application

CashBookController.java


package  com.slsb.expense.tracker.controller;

  

import  com.slsb.expense.tracker.dto.cashbook.CashBookRequestDto;

import  com.slsb.expense.tracker.dto.cashbook.CashBookResponseDto;

import  com.slsb.expense.tracker.service.CashBookService;

import  jakarta.servlet.http.HttpServletRequest;

import  jakarta.validation.Valid;

import  lombok.RequiredArgsConstructor;

import  org.springframework.http.ResponseEntity;

import  org.springframework.web.bind.annotation.*;

  

import  java.nio.file.AccessDeniedException;

import  java.util.List;

  

@RestController

@RequestMapping("/api/cashbook")

@RequiredArgsConstructor

public  class  CashBookController  {

  

private  final  CashBookService cashBookService;

  

@PostMapping("/save")

public  ResponseEntity<CashBookResponseDto>  saveCashBook(

HttpServletRequest  request,

@Valid  @RequestBody  CashBookRequestDto  cashBookRequestDto,

@RequestParam  Long  userId

)  {

return ResponseEntity.ok(cashBookService.saveCashBook(request, cashBookRequestDto, userId));

}

  

@GetMapping("/getAllByUser")

public  ResponseEntity<List<CashBookResponseDto>>  getAllCashBook(

@RequestParam  Long  userId

)  {

return ResponseEntity.ok(cashBookService.getAllByUserCashBook(userId));

}

  

@GetMapping("/getById/{id}")

public  ResponseEntity<CashBookResponseDto>  getCashBookById(

@PathVariable("id")  Long  cashbookId,

@RequestParam  Long  userId

)  throws  AccessDeniedException  {

return ResponseEntity.ok(cashBookService.getCashBookById(cashbookId, userId));

}

  

@PutMapping("/update")

public  ResponseEntity<CashBookResponseDto>  updateCashBook(

HttpServletRequest  request,

@Valid  @RequestBody  CashBookRequestDto  cashBookRequestDto,

@RequestParam  Long  userId

)  throws  AccessDeniedException  {

return ResponseEntity.ok(cashBookService.updateCashBook(request, cashBookRequestDto, userId));

}

  

@DeleteMapping("/deleteById/{id}")

public  ResponseEntity<String>  deleteCashBookById(

@PathVariable("id")  Long  cashbookId,

@RequestParam  Long  userId

)  throws  AccessDeniedException  {

return ResponseEntity.ok(cashBookService.deleteCashBookById(cashbookId, userId));

}

}

CashBookRequestDto.java


package  com.slsb.expense.tracker.dto.cashbook;

  

import  jakarta.validation.constraints.NotBlank;

import  jakarta.validation.constraints.NotNull;

import  lombok.AllArgsConstructor;

import  lombok.Builder;

import  lombok.Data;

import  lombok.NoArgsConstructor;

  

@Data

@Builder

@AllArgsConstructor

@NoArgsConstructor

public  class  CashBookRequestDto  {

  

private  Long cashookId;

  

@NotBlank(message  =  "Cash Book required.")

@NotNull(message  =  "Invalid Cash Book Name: Cash Book Name is NULL")

private  String cashbookName;

}

GlobalExceptionHandler.java


package  com.slsb.expense.tracker.exception;

  

import  io.jsonwebtoken.ExpiredJwtException;

import  jakarta.validation.ConstraintViolation;

import  jakarta.validation.ConstraintViolationException;

import  org.springframework.dao.DataIntegrityViolationException;

import  org.springframework.http.HttpHeaders;

import  org.springframework.http.HttpStatus;

import  org.springframework.http.ResponseEntity;

import  org.springframework.validation.FieldError;

import  org.springframework.web.bind.MethodArgumentNotValidException;

import  org.springframework.web.bind.annotation.ExceptionHandler;

import  org.springframework.web.bind.annotation.RestControllerAdvice;

  

import  java.nio.file.AccessDeniedException;

import  java.util.*;

import  java.util.stream.Collectors;

  

@RestControllerAdvice

public  class  GlobalExceptionHandler  {

  

private  Map<String,  List<String>>  getErrorsMap(List<String>  errors)  {

Map<String,  List<String>> errorResponse =  new  HashMap<>();

errorResponse.put("errors", errors);

return errorResponse;

}

  

// jwt token expired exception

@ExceptionHandler(ExpiredJwtException.class)

public  ResponseEntity<Map<String,  List<String>>>  handleValidationErrorsExpiredJwtException(ExpiredJwtException  ex)  {

List<String> errors = Arrays.asList(ex.getMessage().toString());

return  new  ResponseEntity<>(getErrorsMap(errors),  new  HttpHeaders(), HttpStatus.BAD_REQUEST);

}

  

// dto field null or empty exception

@ExceptionHandler(MethodArgumentNotValidException.class)

public  ResponseEntity<Map<String,  List<String>>>  handleValidationErrorsMethodArgumentNotValidException(MethodArgumentNotValidException  ex)  {

List<String> errors = ex.getBindingResult().getFieldErrors()

.stream()

.map(FieldError::getDefaultMessage)

.collect(Collectors.toList());

return  new  ResponseEntity<>(getErrorsMap(errors),  new  HttpHeaders(), HttpStatus.BAD_REQUEST);

}

  

// entity unique constraint violation exception

@ExceptionHandler(DataIntegrityViolationException.class)

public  ResponseEntity<Map<String,  List<String>>>  handleValidationErrorsDataIntegrityViolationException(DataIntegrityViolationException  ex)  {

List<String> errors;

  

if  (ex.getMessage().contains("email"))  {

errors = Arrays.asList("Email address is already registered.");

}  else  if  (ex.getMessage().contains("expense_name"))  {

errors = Arrays.asList("Expense with this name is already present.");

}  else  if  (ex.getMessage().contains("payment_mode_name"))  {

errors = Arrays.asList("Payment Mode with this name is already present.");

}  else  if  (ex.getMessage().contains("category_name"))  {

errors = Arrays.asList("Category with this name is already present.");

}  else  if  (ex.getMessage().contains("cashbook_name"))  {

errors = Arrays.asList("Cashbook with this name is already present.");

}  else  {

errors = Arrays.asList("Data already present.");

}

  

return  new  ResponseEntity<>(getErrorsMap(errors),  new  HttpHeaders(), HttpStatus.BAD_REQUEST);

}

  

// entity not null and not empty violation exception

@ExceptionHandler(ConstraintViolationException.class)

public  ResponseEntity<Map<String,  List<String>>>  handleValidationErrorsConstraintViolationException(ConstraintViolationException  ex)  {

List<String> errors = ex.getConstraintViolations()

.stream()

.map(ConstraintViolation::getMessage)

.collect(Collectors.toList());

return  new  ResponseEntity<>(getErrorsMap(errors),  new  HttpHeaders(), HttpStatus.BAD_REQUEST);

}

  

// Illegal Argument Exception

@ExceptionHandler(IllegalArgumentException.class)

public  ResponseEntity<Map<String,  List<String>>>  handleValidationErrorsIllegalArgumentException(IllegalArgumentException  ex)  {

List<String> errors = Arrays.asList(ex.getMessage().toString());

return  new  ResponseEntity<>(getErrorsMap(errors),  new  HttpHeaders(), HttpStatus.BAD_REQUEST);

}

  

@ExceptionHandler(NoSuchElementException.class)

public  ResponseEntity<Map<String,  List<String>>>  handleValidationErrorsNoSuchElementException(NoSuchElementException  ex)  {

List<String> errors = Arrays.asList(ex.getMessage().toString());

return  new  ResponseEntity<>(getErrorsMap(errors),  new  HttpHeaders(), HttpStatus.BAD_REQUEST);

}

  

@ExceptionHandler(AccessDeniedException.class)

public  ResponseEntity<Map<String,  List<String>>>  handleValidationErrorsAccessDeniedException(AccessDeniedException  ex)  {

List<String> errors = Arrays.asList(ex.getMessage().toString());

return  new  ResponseEntity<>(getErrorsMap(errors),  new  HttpHeaders(), HttpStatus.BAD_REQUEST);

}

  

// all exception

@ExceptionHandler(Exception.class)

public  ResponseEntity<Map<String,  List<String>>>  handleValidationErrorsException(Exception  ex)  {

List<String> errors = Arrays.asList(ex.getMessage().toString());

return  new  ResponseEntity<>(getErrorsMap(errors),  new  HttpHeaders(), HttpStatus.BAD_REQUEST);

}

}

CashBookRepository.java


package  com.slsb.expense.tracker.repository;

  

import  com.slsb.expense.tracker.entity.CashBook;

import  com.slsb.expense.tracker.jwtAuth.user.User;

import  org.springframework.data.jpa.repository.JpaRepository;

import  org.springframework.data.repository.CrudRepository;

import  org.springframework.stereotype.Repository;

  

import  java.util.List;

  

@Repository

public  interface  CashBookRepository  extends  CrudRepository<CashBook,  Long>  {

List<CashBook>  findByUser(User  user);

}

CashBookService.java


package  com.slsb.expense.tracker.service;

  

import  com.slsb.expense.tracker.dto.cashbook.CashBookRequestDto;

import  com.slsb.expense.tracker.dto.cashbook.CashBookResponseDto;

import  com.slsb.expense.tracker.entity.CashBook;

import  com.slsb.expense.tracker.jwtAuth.user.User;

import  com.slsb.expense.tracker.jwtAuth.user.UserRepository;

import  com.slsb.expense.tracker.repository.CashBookRepository;

import  com.slsb.expense.tracker.util.constant.CashBookConstants;

import  jakarta.servlet.http.HttpServletRequest;

import  lombok.RequiredArgsConstructor;

import  org.springframework.beans.BeanUtils;

import  org.springframework.stereotype.Service;

  

import  java.nio.file.AccessDeniedException;

import  java.util.ArrayList;

import  java.util.Date;

import  java.util.List;

import  java.util.stream.Collectors;

  

@Service

@RequiredArgsConstructor

public  class  CashBookService  {

  

private  final  UserRepository userRepository;

private  final  CashBookRepository cashBookRepository;

  

public  CashBookResponseDto  saveCashBook(HttpServletRequest  request,  CashBookRequestDto  cashBookRequestDto,  Long  userId)  {

User user = userRepository.findById(userId).get();

  

var cashbook = CashBook.builder()

.cashbookName(cashBookRequestDto.getCashbookName())

.user(user)

.activeFlag(CashBookConstants.active_flag_active)

.createdDate(new  Date())

.createdByIp(request.getRemoteAddr())

.build();

  

CashBook resultCashBook = cashBookRepository.save(cashbook);

  

CashBookResponseDto cashBookResponseDto =  new  CashBookResponseDto();

BeanUtils.copyProperties(resultCashBook, cashBookResponseDto);

  

return cashBookResponseDto;

}

  

public  List<CashBookResponseDto>  getAllByUserCashBook(Long  userId)  {

List<CashBook> cashBookList = cashBookRepository.findByUser(userRepository.findById(userId).get());

  

List<CashBookResponseDto> cashBookResponseDtoList =  new  ArrayList<>();

  

cashBookResponseDtoList = cashBookList

  

.stream()

.map(cashBook ->  {

CashBookResponseDto cashBookResponseDto =  new  CashBookResponseDto();

BeanUtils.copyProperties(cashBook,cashBookResponseDto);

return cashBookResponseDto;

})

.collect(Collectors.toList());

  

return cashBookResponseDtoList;

}

  

private  void  validateCashBookUser(Long  cashbookId,  Long  userId)  throws  AccessDeniedException  {

CashBook cashBook = cashBookRepository.findById(cashbookId).get();

if  (cashBook.getUser().getUserId()  != userId)  {

throw  new  AccessDeniedException("You don't have access of this Cash Book.");

}

}

  

public  CashBookResponseDto  getCashBookById(Long  cashbookId,  Long  userId)  throws  AccessDeniedException  {

CashBookResponseDto cashBookResponseDto =  new  CashBookResponseDto();

CashBook cashBook = cashBookRepository.findById(cashbookId).get();

validateCashBookUser(cashbookId, userId);

BeanUtils.copyProperties(cashBook, cashBookResponseDto);

return cashBookResponseDto;

}

  

public  CashBookResponseDto  updateCashBook(HttpServletRequest  request,  CashBookRequestDto  cashBookRequestDto,  Long  userId)  throws  AccessDeniedException  {

User user = userRepository.findById(userId).get();

CashBook cashBook = cashBookRepository.findById(cashBookRequestDto.getCashookId()).get();

validateCashBookUser(cashBookRequestDto.getCashookId(), userId);

  

cashBook.setCashbookName(cashBookRequestDto.getCashbookName());

cashBook.setUpdatedDate(new  Date());

cashBook.setUpdatedByIp(request.getRemoteAddr());

  

CashBook resultCashBook = cashBookRepository.save(cashBook);

CashBookResponseDto cashBookResponseDto =  new  CashBookResponseDto();

BeanUtils.copyProperties(resultCashBook, cashBookResponseDto);

  

return cashBookResponseDto;

}

  

public  String  deleteCashBookById(Long  cashbookId,  Long  userId)  throws  AccessDeniedException  {

String cashBookName = cashBookRepository.findById(cashbookId).get().getCashbookName();

validateCashBookUser(cashbookId, userId);

cashBookRepository.deleteById(cashbookId);

return  "Cash Book "  + cashBookName +  " successfully deleted.";

}

}

application.properties


spring.application.name=Expense  Tracker

server.port=8081

  

# JPA  Datasource  Configuration

spring.datasource.url=jdbc:postgresql://localhost:5432/expense-tracker

spring.datasource.username=expense_tracker

spring.datasource.password=slsb$ex@tr

spring.datasource.driver-class-name=org.postgresql.Driver

  

## Hibernate properties

spring.jpa.hibernate.ddl-auto=update

spring.jpa.open-in-view=false

spring.jpa.show-sql=true

spring.jpa.properties.hibernate.format_sql=true

spring.jpa.database=postgresql

spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect

data.sql


INSERT  INTO  category  ("CATEGORY_NAME")  VALUES  ('Fashion');

INSERT  INTO  category  ("CATEGORY_NAME")  VALUES  ('Electronics');

INSERT  INTO  category  ("CATEGORY_NAME")  VALUES  ('Books');

INSERT  INTO  category  ("CATEGORY_NAME")  VALUES  ('Groceries');

INSERT  INTO  category  ("CATEGORY_NAME")  VALUES  ('Medicines');

  

INSERT  INTO  user  ("USERNAME",  "PASSWORD")  VALUES  ('jack',  'pass_word');

INSERT  INTO  user  ("USERNAME",  "PASSWORD")  VALUES  ('bob',  'pass_word');

INSERT  INTO  user  ("USERNAME",  "PASSWORD")  VALUES  ('apple',  'pass_word');

INSERT  INTO  user  ("USERNAME",  "PASSWORD")  VALUES  ('glaxo',  'pass_word');

  

INSERT  INTO  cart  ("TOTAL_AMOUNT",  "USER_USER_ID"  )  VALUES  (20,  1);

INSERT  INTO  cart  ("TOTAL_AMOUNT",  "USER_USER_ID")  VALUES  (0,  2);

  

INSERT  INTO  USER_ROLE  ("USER_ID",  "ROLES")  VALUES  (1,  'CONSUMER');

INSERT  INTO  USER_ROLE  ("USER_ID",  "ROLES")  VALUES  (2,  'CONSUMER');

INSERT  INTO  USER_ROLE  ("USER_ID",  "ROLES")  VALUES  (3,  'SELLER');

INSERT  INTO  USER_ROLE  ("USER_ID",  "ROLES")  VALUES  (4,  'SELLER');

  

INSERT  INTO  PRODUCT  ("PRICE",  "PRODUCT_NAME",  "CATEGORY_ID",  "SELLER_ID")  VALUES  (29190,  'Apple iPad 10.2 8th Gen WiFi iOS Tablet',  2,  3);

INSERT  INTO  PRODUCT  ("PRICE",  "PRODUCT_NAME",  "CATEGORY_ID",  "SELLER_ID")  VALUES  (10,  'Crocin pain relief tablet',  5,  4);

  

INSERT  INTO  CART_PRODUCT  ("CART_ID",  "PRODUCT_ID",  "QUANTITY")  VALUES  (1,  2,  2);