diff --git a/src/main/java/org/cerberus/controllers/ApplicationController.java b/src/main/java/org/cerberus/controllers/ApplicationController.java index 1627aab..88e60bf 100644 --- a/src/main/java/org/cerberus/controllers/ApplicationController.java +++ b/src/main/java/org/cerberus/controllers/ApplicationController.java @@ -1,5 +1,7 @@ package org.cerberus.controllers; +import com.fasterxml.jackson.annotation.JsonView; +import org.cerberus.entities.dto.View; import org.cerberus.entities.persistence.Application; import org.cerberus.entities.persistence.User; import org.cerberus.services.ApplicationService; @@ -16,36 +18,39 @@ import static org.cerberus.core.constant.RoleSecurity.MAINTAINER; @RestController @RequestMapping("/api/applications") public class ApplicationController { - private ApplicationService applicationService; + private ApplicationService service; private SecurityService securityService; - public ApplicationController(ApplicationService applicationService, - SecurityService securityService) { - this.applicationService = applicationService; + ApplicationController(ApplicationService service, + SecurityService securityService) { + this.service = service; this.securityService = securityService; } @GetMapping("/{id}") + @JsonView({View.ApplicationDTO.class}) public Application findById(@PathVariable("id") UUID id) { - return applicationService.findByIdOrElseThrow(id); + return service.findByIdOrElseThrow(id); } @PostMapping + @JsonView({View.ApplicationDTO.class}) public Application create(@RequestBody Application application, Principal connectedUser) { User user = securityService.getAdminUser(connectedUser); - return applicationService.create(application, user); + return service.create(application, user); } @PutMapping + @JsonView({View.ApplicationDTO.class}) public Application update(@RequestBody Application application, Principal connectedUser) { securityService.checkHasAnyRole(connectedUser, application, ADMIN, MAINTAINER); - return applicationService.update(application); + return service.update(application); } @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void delete(@PathVariable("id") UUID id, Principal connectedUser) { securityService.checkHasAnyRole(connectedUser, id, ADMIN, MAINTAINER); - applicationService.delete(id); + service.delete(id); } } diff --git a/src/main/java/org/cerberus/controllers/ConfigurationFileController.java b/src/main/java/org/cerberus/controllers/ConfigurationFileController.java index 9fe8e57..b29d994 100644 --- a/src/main/java/org/cerberus/controllers/ConfigurationFileController.java +++ b/src/main/java/org/cerberus/controllers/ConfigurationFileController.java @@ -1,8 +1,11 @@ package org.cerberus.controllers; +import com.fasterxml.jackson.annotation.JsonView; +import org.cerberus.entities.dto.View; import org.cerberus.entities.persistence.ConfigurationFile; import org.cerberus.services.ConfigurationFileService; import org.cerberus.services.SecurityService; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import java.security.Principal; @@ -14,28 +17,48 @@ import static org.cerberus.core.constant.RoleSecurity.MAINTAINER; @RestController @RequestMapping("/api/applications/{applicationId}/configurationFile") public class ConfigurationFileController { - private ConfigurationFileService configurationFileService; + private ConfigurationFileService service; private SecurityService securityService; - ConfigurationFileController(ConfigurationFileService configurationFileService, + ConfigurationFileController(ConfigurationFileService service, SecurityService securityService) { - this.configurationFileService = configurationFileService; + this.service = service; this.securityService = securityService; } @GetMapping("/{id}") + @JsonView({View.ConfigurationFileDTO.class}) public ConfigurationFile findById(@PathVariable("applicationId") UUID applicationId, @PathVariable("id") UUID configurationFileId, Principal connectedUser) { securityService.checkHasAnyRole(connectedUser, applicationId, ADMIN, MAINTAINER); - return configurationFileService.findByApplicationIdAndId(applicationId, configurationFileId); + return service.findByApplicationIdAndId(applicationId, configurationFileId); } @PostMapping - public void create(@PathVariable("applicationId") UUID applicationId, + @JsonView({View.ConfigurationFileDTO.class}) + public ConfigurationFile create(@PathVariable("applicationId") UUID applicationId, @RequestBody ConfigurationFile configurationFile, Principal connectedUser) { securityService.checkHasAnyRole(connectedUser, applicationId, ADMIN, MAINTAINER); - configurationFileService.create(applicationId, configurationFile); + return service.create(applicationId, configurationFile); + } + + @PutMapping + @JsonView({View.ConfigurationFileDTO.class}) + public ConfigurationFile update(@PathVariable("applicationId") UUID applicationId, + @RequestBody ConfigurationFile configurationFile, + Principal connectedUser) { + securityService.checkHasAnyRole(connectedUser, applicationId, ADMIN, MAINTAINER); + return service.update(applicationId, configurationFile); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable("applicationId") UUID applicationId, + @PathVariable("id") UUID configurationFileId, + Principal connectedUser) { + securityService.checkHasAnyRole(connectedUser, applicationId, ADMIN, MAINTAINER); + service.delete(applicationId, configurationFileId); } } diff --git a/src/main/java/org/cerberus/controllers/UserController.java b/src/main/java/org/cerberus/controllers/UserController.java index 2d97926..03809a1 100644 --- a/src/main/java/org/cerberus/controllers/UserController.java +++ b/src/main/java/org/cerberus/controllers/UserController.java @@ -16,16 +16,16 @@ import static org.springframework.http.HttpStatus.NO_CONTENT; @RestController @RequestMapping("/api/users") public class UserController { - private UserService userService; + private UserService service; - public UserController(UserService userService) { - this.userService = userService; + public UserController(UserService service) { + this.service = service; } @PostMapping("/login") @ResponseStatus(NO_CONTENT) public void login(@RequestBody User user) { - userService.authenticate(user); + service.authenticate(user); } @GetMapping("/disconnection") @@ -40,6 +40,6 @@ public class UserController { @PostMapping("/signup") @ResponseStatus(NO_CONTENT) public void signUp(@RequestBody SignUpDTO inputData) { - userService.signUp(inputData); + service.signUp(inputData); } } diff --git a/src/main/java/org/cerberus/entities/dto/View.java b/src/main/java/org/cerberus/entities/dto/View.java new file mode 100644 index 0000000..e50a9ce --- /dev/null +++ b/src/main/java/org/cerberus/entities/dto/View.java @@ -0,0 +1,7 @@ +package org.cerberus.entities.dto; + +public final class View { + private View() {} + public interface ApplicationDTO {} + public interface ConfigurationFileDTO {} +} diff --git a/src/main/java/org/cerberus/entities/persistence/Application.java b/src/main/java/org/cerberus/entities/persistence/Application.java index f275675..7a7f070 100644 --- a/src/main/java/org/cerberus/entities/persistence/Application.java +++ b/src/main/java/org/cerberus/entities/persistence/Application.java @@ -1,5 +1,7 @@ package org.cerberus.entities.persistence; +import com.fasterxml.jackson.annotation.JsonView; +import org.cerberus.entities.dto.View; import org.hibernate.annotations.Proxy; import javax.persistence.*; @@ -14,12 +16,15 @@ import static javax.persistence.CascadeType.REMOVE; @Proxy(lazy = false) public class Application { @Id + @JsonView({View.ApplicationDTO.class, View.ConfigurationFileDTO.class}) private UUID id; - @Column(nullable = false) + @Column(nullable = false, unique = true) + @JsonView({View.ApplicationDTO.class}) private String name; - @Column(nullable = false) + @Column(nullable = false, unique = true) + @JsonView({View.ApplicationDTO.class}) private String serviceName; @OneToMany(mappedBy = "application", cascade = { REMOVE }) diff --git a/src/main/java/org/cerberus/entities/persistence/ConfigurationFile.java b/src/main/java/org/cerberus/entities/persistence/ConfigurationFile.java index da90f61..c1de892 100644 --- a/src/main/java/org/cerberus/entities/persistence/ConfigurationFile.java +++ b/src/main/java/org/cerberus/entities/persistence/ConfigurationFile.java @@ -1,5 +1,7 @@ package org.cerberus.entities.persistence; +import com.fasterxml.jackson.annotation.JsonView; +import org.cerberus.entities.dto.View; import org.hibernate.annotations.Proxy; import javax.persistence.*; @@ -10,13 +12,16 @@ import java.util.UUID; @Proxy(lazy = false) public class ConfigurationFile { @Id + @JsonView({View.ConfigurationFileDTO.class}) private UUID id; @Column(nullable = false) + @JsonView({View.ConfigurationFileDTO.class}) private String path; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "application_id") + @JsonView({ConfigurationFile.class}) private Application application; @PrePersist diff --git a/src/main/java/org/cerberus/repositories/ApplicationRepository.java b/src/main/java/org/cerberus/repositories/ApplicationRepository.java index 8818de9..94a89ec 100644 --- a/src/main/java/org/cerberus/repositories/ApplicationRepository.java +++ b/src/main/java/org/cerberus/repositories/ApplicationRepository.java @@ -4,10 +4,15 @@ import org.cerberus.entities.persistence.Application; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import java.util.UUID; +@Repository public interface ApplicationRepository extends JpaRepository { @Query(value = "SELECT EXISTS(SELECT id FROM application WHERE name = :name)", nativeQuery = true) - Boolean existsByName(@Param("name") String name); + boolean existsByName(@Param("name") String name); + + @Query(value = "SELECT EXISTS(SELECT id FROM application WHERE service_name = :serviceName)", nativeQuery = true) + boolean existsByServiceName(@Param("serviceName") String serviceName); } diff --git a/src/main/java/org/cerberus/repositories/ConfigurationFileRepository.java b/src/main/java/org/cerberus/repositories/ConfigurationFileRepository.java index 0ed0785..68d36f9 100644 --- a/src/main/java/org/cerberus/repositories/ConfigurationFileRepository.java +++ b/src/main/java/org/cerberus/repositories/ConfigurationFileRepository.java @@ -2,11 +2,20 @@ package org.cerberus.repositories; import org.cerberus.entities.persistence.ConfigurationFile; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.UUID; @Repository public interface ConfigurationFileRepository extends JpaRepository { + @Query(value = "SELECT EXISTS(SELECT id FROM configuration_file WHERE path = :path AND application_id = :applicationId)", nativeQuery = true) + boolean existsByPathAndApplicationId(@Param("path") String path, + @Param("applicationId") UUID applicationId); + @Query(value = "SELECT EXISTS(SELECT id FROM configuration_file WHERE id = :configurationFileId " + + "AND application_id = :applicationId)", nativeQuery = true) + boolean doesBelongToApplication(@Param("configurationFileId") UUID configurationFileId, + @Param("applicationId") UUID applicationId); } diff --git a/src/main/java/org/cerberus/repositories/UserRepository.java b/src/main/java/org/cerberus/repositories/UserRepository.java index ca732d8..5417034 100644 --- a/src/main/java/org/cerberus/repositories/UserRepository.java +++ b/src/main/java/org/cerberus/repositories/UserRepository.java @@ -5,11 +5,13 @@ import org.cerberus.entities.persistence.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.UUID; +@Repository public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.email = :email") Optional findByEmail(@Param("email") String email); diff --git a/src/main/java/org/cerberus/services/ApplicationService.java b/src/main/java/org/cerberus/services/ApplicationService.java index 797d3c8..9ee6b66 100644 --- a/src/main/java/org/cerberus/services/ApplicationService.java +++ b/src/main/java/org/cerberus/services/ApplicationService.java @@ -13,28 +13,32 @@ import static org.cerberus.core.utils.StringUtils.concat; @Service public class ApplicationService extends AbstractService { - private ApplicationRepository applicationRepository; + private ApplicationRepository repository; private ApplicationRoleService applicationRoleService; - private ApplicationValidator applicationValidator; + private ApplicationValidator validator; - ApplicationService(ApplicationRepository applicationRepository, + ApplicationService(ApplicationRepository repository, ApplicationRoleService applicationRoleService, - ApplicationValidator applicationValidator) { - super(applicationRepository); - this.applicationRepository = applicationRepository; + ApplicationValidator validator) { + super(repository); + this.repository = repository; this.applicationRoleService = applicationRoleService; - this.applicationValidator = applicationValidator; + this.validator = validator; } @Transactional public Application create(Application application, User user) { - applicationValidator.validate(application); + validator.validate(application); - if(applicationRepository.existsByName(application.getName())) { + if(repository.existsByName(application.getName())) { throw new BadRequestException(concat("The application ", application.getName(), " already exists.")); } + if(repository.existsByServiceName(application.getServiceName())) { + throw new BadRequestException(concat("The service name ", application.getServiceName(), + " already exists for another application.")); + } - applicationRepository.save(application); + repository.save(application); // Application creator is by default a maintainer applicationRoleService.create(application, user, MAINTAINER); @@ -42,9 +46,21 @@ public class ApplicationService extends AbstractService { } public Application update(Application application) { - applicationValidator.validate(application); - applicationValidator.sanitize(application); - applicationRepository.save(application); - return application; + validator.validate(application); + validator.sanitize(application); + + Application appFromDb = findByIdOrElseThrow(application.getId()); + // If the app name changed + if(!appFromDb.getName().equals(application.getName()) && repository.existsByName(application.getName())) { + throw new BadRequestException(concat("The application ", application.getName(), " already exists.")); + } + // If the app service name changed + if(!appFromDb.getServiceName().equals(application.getServiceName()) + && repository.existsByServiceName(application.getServiceName())) { + throw new BadRequestException(concat("The service name ", application.getServiceName(), + " already exists for another application.")); + } + + return repository.save(application); } } diff --git a/src/main/java/org/cerberus/services/ConfigurationFileService.java b/src/main/java/org/cerberus/services/ConfigurationFileService.java index 560959a..63d7c1f 100644 --- a/src/main/java/org/cerberus/services/ConfigurationFileService.java +++ b/src/main/java/org/cerberus/services/ConfigurationFileService.java @@ -12,16 +12,16 @@ import java.util.UUID; @Service public class ConfigurationFileService extends AbstractService { private ApplicationService applicationService; - private ConfigurationFileRepository configurationFileRepository; - private ConfigurationFileValidator configurationFileValidator; + private ConfigurationFileRepository repository; + private ConfigurationFileValidator validator; ConfigurationFileService(ApplicationService applicationService, - ConfigurationFileRepository configurationFileRepository, - ConfigurationFileValidator configurationFileValidator) { - super(configurationFileRepository); + ConfigurationFileRepository repository, + ConfigurationFileValidator validator) { + super(repository); this.applicationService = applicationService; - this.configurationFileRepository = configurationFileRepository; - this.configurationFileValidator = configurationFileValidator; + this.repository = repository; + this.validator = validator; } public ConfigurationFile findByApplicationIdAndId(UUID applicationId, UUID configurationFileId) { @@ -31,15 +31,36 @@ public class ConfigurationFileService extends AbstractService return findByIdOrElseThrow(configurationFileId); } - public void create(UUID applicationId, ConfigurationFile configurationFile) { - if(applicationId == null || StringUtils.isNull(applicationId.toString())) { - throw new BadRequestException("Application id is required."); - } - configurationFileValidator.validate(configurationFile); + public ConfigurationFile create(UUID applicationId, ConfigurationFile configurationFile) { + return save(applicationId, configurationFile, false); + } - configurationFile.setApplication(applicationService.findById(applicationId) - .orElseThrow(() -> new BadRequestException("The application doesn't exist.")) - ); - configurationFileRepository.save(configurationFile); + public ConfigurationFile update(UUID applicationId, ConfigurationFile configurationFile) { + return save(applicationId, configurationFile, true); + } + + private ConfigurationFile save(UUID applicationId, ConfigurationFile configurationFile, boolean isUpdate) { + if(applicationId == null || StringUtils.isNull(applicationId.toString()) || + (isUpdate && !repository.doesBelongToApplication(configurationFile.getId(), applicationId))) { + throwNotFoundException(); + } + validator.validate(configurationFile); + + if(repository.existsByPathAndApplicationId(configurationFile.getPath(), applicationId)) { + throw new BadRequestException("Configuration file already exists."); + } + + configurationFile.setApplication(applicationService.findByIdOrElseThrow(applicationId)); + return repository.save(configurationFile); + } + + public void delete(UUID applicationId, UUID configurationFileId) { + if(applicationId == null || StringUtils.isNull(applicationId.toString()) + || configurationFileId == null || StringUtils.isNull(configurationFileId.toString()) + || !repository.doesBelongToApplication(configurationFileId, applicationId)) { + throwNotFoundException(); + } + + repository.deleteById(configurationFileId); } } diff --git a/src/main/java/org/cerberus/services/UserService.java b/src/main/java/org/cerberus/services/UserService.java index 97daf6e..04a957c 100644 --- a/src/main/java/org/cerberus/services/UserService.java +++ b/src/main/java/org/cerberus/services/UserService.java @@ -25,16 +25,16 @@ public class UserService { private CustomAuthenticationProvider authenticationProvider; private SignUpMapper signUpMapper; private SignUpValidator signUpValidator; - private UserRepository userRepository; + private UserRepository repository; public UserService(CustomAuthenticationProvider authenticationProvider, SignUpMapper signUpMapper, SignUpValidator signUpValidator, - UserRepository userRepository) { + UserRepository repository) { this.authenticationProvider = authenticationProvider; this.signUpMapper = signUpMapper; this.signUpValidator = signUpValidator; - this.userRepository = userRepository; + this.repository = repository; } public void authenticate(User user) { @@ -48,7 +48,7 @@ public class UserService { } User checkCredentials(String email, String password) { - Optional optUser = userRepository.findByEmail(email); + Optional optUser = repository.findByEmail(email); if(optUser.isEmpty() || !optUser.get().getPassword().equals(password)) { throw new BadRequestException("Credentials are incorrect."); @@ -73,24 +73,24 @@ public class UserService { } public List getApplicationRolesByEmail(String email) { - return userRepository.getApplicationRolesByEmail(email); + return repository.getApplicationRolesByEmail(email); } public void signUp(SignUpDTO inputData) { signUpValidator.validate(inputData); - if(userRepository.isEmailAlreadyExists(inputData.getEmail())) { + if(repository.isEmailAlreadyExists(inputData.getEmail())) { throw new BadRequestException("Email is already assigned to another user."); } - userRepository.save(signUpMapper.toUser(inputData)); + repository.save(signUpMapper.toUser(inputData)); } public boolean isAdmin(User user) { - return userRepository.isAdmin(user.getId()); + return repository.isAdmin(user.getId()); } public Optional findByEmail(String email) { - return userRepository.findByEmail(email); + return repository.findByEmail(email); } } diff --git a/src/main/sql/1.0.0-SNAPSHOT b/src/main/sql/1.0.0-SNAPSHOT index aa16ca2..9c0baf9 100644 --- a/src/main/sql/1.0.0-SNAPSHOT +++ b/src/main/sql/1.0.0-SNAPSHOT @@ -11,7 +11,7 @@ CREATE TABLE "user" ( CREATE TABLE application ( id uuid DEFAULT uuid_generate_v4(), name VARCHAR NOT NULL UNIQUE, - service_name VARCHAR NOT NULL, + service_name VARCHAR NOT NULL UNIQUE, CONSTRAINT application_pk PRIMARY KEY (id) ); @@ -20,7 +20,8 @@ CREATE TABLE configuration_file ( path VARCHAR NOT NULL, application_id uuid NOT NULL, CONSTRAINT configuration_file_pk PRIMARY KEY (id), - CONSTRAINT configuration_file_application_id_fk FOREIGN KEY (application_id) REFERENCES application (id) + CONSTRAINT configuration_file_application_id_fk FOREIGN KEY (application_id) REFERENCES application (id), + CONSTRAINT configuration_file_path_application_id_unique UNIQUE (path, application_id) ); CREATE INDEX configuration_file_application_id_idx ON configuration_file(application_id);