Skip to content

Commit

Permalink
Feature: Initial Application Major Setup
Browse files Browse the repository at this point in the history
# Changes

* Generic classes for Entity, Repository, Service, Controller, Specification Builder, Mapper.
* Added auditor to audit created date, last modified date, created by user, last modified by user.
* Integrated OAuth with Spring Security (in-memory auth).
* Integrated liquibase for database version control.
* Added patch for initial super admin user creation.
* Exposed authenticated API for User CRUD.
* Added postman collection for currently exposed APIs.
  • Loading branch information
elwyncrestha committed Jun 21, 2020
1 parent 0966cf6 commit d55993a
Show file tree
Hide file tree
Showing 40 changed files with 1,320 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.pemits.webcare.api.user.entity;

import java.util.Collection;
import javax.persistence.Entity;
import javax.persistence.Table;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.pemits.webcare.core.entity.BaseEntity;
import com.pemits.webcare.core.enums.Status;

/**
* @author Elvin Shrestha on 6/21/2020
*/
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity<Long> implements UserDetails {

private String username;
private String password;
private String name;
private String email;
private Status status;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}

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

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

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

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

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

@Override
public boolean isEnabled() {
return status.equals(Status.ACTIVE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.pemits.webcare.api.user.repository;

import org.springframework.stereotype.Repository;

import com.pemits.webcare.api.user.entity.User;
import com.pemits.webcare.core.repository.BaseRepository;

/**
* @author Elvin Shrestha on 6/21/2020
*/
@Repository
public interface UserRepository extends BaseRepository<User, Long> {

User findUserByUsername(String username);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.pemits.webcare.api.user.repository.spec;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.data.jpa.domain.Specification;

import com.pemits.webcare.api.user.entity.User;

/**
* @author Elvin Shrestha on 6/21/2020
*/
public class UserSpec implements Specification<User> {

private final String property;
private final String value;

public UserSpec(String property, String value) {
this.property = property;
this.value = value;
}

@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery,
CriteriaBuilder criteriaBuilder) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.pemits.webcare.api.user.repository.spec;

import java.util.Map;

import org.springframework.data.jpa.domain.Specification;

import com.pemits.webcare.api.user.entity.User;
import com.pemits.webcare.core.repository.BaseSpecBuilder;

/**
* @author Elvin Shrestha on 6/21/2020
*/
public class UserSpecBuilder extends BaseSpecBuilder<User> {

public UserSpecBuilder(Map<String, String> params) {
super(params);
}

@Override
protected Specification<User> getSpecification(String property, String filterValue) {
return new UserSpec(property, filterValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.pemits.webcare.api.user.service;

import org.springframework.beans.factory.annotation.Autowired;
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.Component;

import com.pemits.webcare.api.user.repository.UserRepository;

/**
* @author Elvin Shrestha on 6/21/2020
*/
@Component
public class UserDetailServiceImpl implements UserDetailsService {

@Autowired
private UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findUserByUsername(username);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.pemits.webcare.api.user.service;

import com.pemits.webcare.api.user.entity.User;
import com.pemits.webcare.core.service.BaseService;

/**
* @author Elvin Shrestha on 6/21/2020
*/
public interface UserService extends BaseService<User, Long> {

User findByUsername(String username);

User getAuthenticated();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.pemits.webcare.api.user.service;

import java.util.Map;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.pemits.webcare.api.user.entity.User;
import com.pemits.webcare.api.user.repository.UserRepository;
import com.pemits.webcare.api.user.repository.spec.UserSpecBuilder;
import com.pemits.webcare.core.enums.Status;
import com.pemits.webcare.core.repository.BaseSpecBuilder;
import com.pemits.webcare.core.service.BaseServiceImpl;

/**
* @author Elvin Shrestha on 6/21/2020
*/
@Service
public class UserServiceImpl extends BaseServiceImpl<User, Long> implements UserService {

private final UserRepository repository;
private final PasswordEncoder passwordEncoder;

protected UserServiceImpl(
UserRepository repository,
PasswordEncoder passwordEncoder) {
super(repository);
this.repository = repository;
this.passwordEncoder = passwordEncoder;
}

@Override
public User save(User user) {
if (user.getId() == null) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setStatus(Status.ACTIVE);
} else {
user.setPassword(repository.getOne(user.getId()).getPassword());
}
return repository.save(user);
}

@Override
protected BaseSpecBuilder<User> getSpec(Map<String, String> filterParams) {
return new UserSpecBuilder(filterParams);
}

@Override
public User findByUsername(String username) {
return repository.findUserByUsername(username);
}

@Override
public User getAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof UserDetails) {
User user = (User) authentication.getPrincipal();
user = this.findByUsername(user.getUsername());
return user;
} else {
throw new UsernameNotFoundException(
"User is not authenticated; Found of type " + authentication.getPrincipal()
.getClass() + "; Expected type User");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.pemits.webcare.core.config;

import static com.pemits.webcare.core.constant.AppConstant.USERNAME;

import java.util.Map;
import java.util.Optional;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import com.pemits.webcare.core.dao.UserDao;
import com.pemits.webcare.core.exception.InvalidTokenException;

/**
* @author Elvin Shrestha on 6/21/2020
*/
@Component
public class CustomAuditorAware implements AuditorAware<Long> {

@Autowired
private UserDao userDao;

@Override
public Optional<Long> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

ObjectMapper objectMapper = new ObjectMapper();
if (authentication instanceof AnonymousAuthenticationToken) {
return Optional.empty();
}

Map<String, Object> map = objectMapper
.convertValue(authentication.getPrincipal(), Map.class);
if (!map.isEmpty()) {
Long id = userDao.getAuthenticatedUserId(map.get(USERNAME).toString());
return Optional.of(id);
}

throw new InvalidTokenException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.pemits.webcare.core.constant;

/**
* @author Elvin Shrestha on 6/21/2020
*/
public class AppConstant {

public static final String USERNAME = "username";
public static final String SPRING_MODEL = "spring";
public static final String SUCCESSFUL = "Successful";

private AppConstant() {}
}
Loading

0 comments on commit d55993a

Please sign in to comment.