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;
}
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;
}
@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);
}
@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);
}
@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);
}
@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);
}
@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);
}
@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
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
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);