diff --git a/pom.xml b/pom.xml index 75e61a3..6940cf7 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 4.0.0 org.teapot teapot-backend - 1.0.3 + 2.0.0 jar diff --git a/src/main/java/org/teapot/backend/config/DevelopmentProfileConfig.java b/src/main/java/org/teapot/backend/config/DevelopmentProfileConfig.java index a49ffb4..61b498f 100644 --- a/src/main/java/org/teapot/backend/config/DevelopmentProfileConfig.java +++ b/src/main/java/org/teapot/backend/config/DevelopmentProfileConfig.java @@ -11,16 +11,20 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.teapot.backend.model.meta.TeapotAction; import org.teapot.backend.model.meta.TeapotProperty; -import org.teapot.backend.model.User; -import org.teapot.backend.model.UserAuthority; import org.teapot.backend.model.meta.TeapotResource; -import org.teapot.backend.repository.TeapotActionRepository; -import org.teapot.backend.repository.TeapotPropertyRepository; -import org.teapot.backend.repository.TeapotResourceRepository; -import org.teapot.backend.repository.UserRepository; +import org.teapot.backend.model.organization.Member; +import org.teapot.backend.model.organization.MemberStatus; +import org.teapot.backend.model.organization.Organization; +import org.teapot.backend.model.user.User; +import org.teapot.backend.model.user.UserAuthority; +import org.teapot.backend.repository.meta.TeapotActionRepository; +import org.teapot.backend.repository.meta.TeapotPropertyRepository; +import org.teapot.backend.repository.meta.TeapotResourceRepository; +import org.teapot.backend.repository.organization.MemberRepository; +import org.teapot.backend.repository.organization.OrganizationRepository; +import org.teapot.backend.repository.user.UserRepository; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.Arrays; @@ -43,6 +47,12 @@ public class DevelopmentProfileConfig { @Autowired private PasswordEncoder passwordEncoder; + @Autowired + private OrganizationRepository organizationRepository; + + @Autowired + private MemberRepository memberRepository; + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -70,6 +80,8 @@ public CommandLineRunner loadData() { addProperties(); addResources(); addActions(); + addOrganizations(); + addMembers(); }; } @@ -82,7 +94,7 @@ private void registerAdmin() { admin.setActivated(true); admin.setFirstName("Cake"); admin.setLastName("Lover"); - admin.setRegistrationDate(LocalDateTime.now()); + admin.setRegistrationDate(LocalDate.now()); admin.setBirthday(LocalDate.now()); admin.setDescription("i manage everything"); admin.setAuthority(UserAuthority.ADMIN); @@ -99,7 +111,7 @@ private void registerDaleCooper() { user.setActivated(true); user.setFirstName("Dale"); user.setLastName("Cooper"); - user.setRegistrationDate(LocalDateTime.now()); + user.setRegistrationDate(LocalDate.now()); user.setBirthday(LocalDate.now()); user.setDescription("a special FBI agent"); user.setAuthority(UserAuthority.USER); @@ -116,7 +128,7 @@ private void registerLoraPalmer() { user.setActivated(true); user.setFirstName("Lora"); user.setLastName("Palmer"); - user.setRegistrationDate(LocalDateTime.now()); + user.setRegistrationDate(LocalDate.now()); user.setBirthday(LocalDate.now()); user.setDescription("a dead girl"); user.setAuthority(UserAuthority.USER); @@ -133,7 +145,7 @@ private void registerSherlockHolmes() { user.setActivated(true); user.setFirstName("Sherlock"); user.setLastName("Holmes"); - user.setRegistrationDate(LocalDateTime.now()); + user.setRegistrationDate(LocalDate.now()); user.setBirthday(LocalDate.now()); user.setDescription("private detective"); user.setAuthority(UserAuthority.USER); @@ -150,7 +162,7 @@ private void registerDoctorWatson() { user.setActivated(true); user.setFirstName("John"); user.setLastName("Watson"); - user.setRegistrationDate(LocalDateTime.now()); + user.setRegistrationDate(LocalDate.now()); user.setBirthday(LocalDate.now()); user.setDescription("Sherlock Holmes' mate"); user.setAuthority(UserAuthority.USER); @@ -166,7 +178,7 @@ private void addProperties() { property1.setValue("1"); property2.setName("site-uri"); - property2.setValue("localhost:8080"); + property2.setValue("http://localhost:8080"); propertyRepository.save(property1); propertyRepository.save(property2); @@ -196,4 +208,46 @@ private void addActions() { actionRepository.save(Arrays.asList(action1, action2)); } + + private void addOrganizations() { + Organization teapot = new Organization(); + teapot.setName("teapot"); + teapot.setCreationDate(LocalDate.now()); + organizationRepository.save(teapot); + + Organization someOrganization = new Organization(); + someOrganization.setName("someOrganization"); + someOrganization.setCreationDate(LocalDate.now()); + organizationRepository.save(someOrganization); + } + + private void addMembers() { + Member member1 = new Member(); + member1.setAdmissionDate(LocalDate.now()); + member1.setOrganization(organizationRepository.findByName("teapot")); + member1.setUser(userRepository.findByUsername("dr_watson")); + member1.setStatus(MemberStatus.CREATOR); + memberRepository.save(member1); + + Member member2 = new Member(); + member2.setAdmissionDate(LocalDate.now()); + member2.setOrganization(organizationRepository.findByName("teapot")); + member2.setUser(userRepository.findByUsername("lora_palmer")); + member2.setStatus(MemberStatus.WORKER); + memberRepository.save(member2); + + Member member3 = new Member(); + member3.setAdmissionDate(LocalDate.now()); + member3.setOrganization(organizationRepository.findByName("someOrganization")); + member3.setUser(userRepository.findByUsername("dale_cooper")); + member3.setStatus(MemberStatus.CREATOR); + memberRepository.save(member3); + + Member member4 = new Member(); + member4.setAdmissionDate(LocalDate.now()); + member4.setOrganization(organizationRepository.findByName("someOrganization")); + member4.setUser(userRepository.findByUsername("sherlock_holmes")); + member4.setStatus(MemberStatus.OWNER); + memberRepository.save(member4); + } } diff --git a/src/main/java/org/teapot/backend/config/security/AuthorizationServerConfig.java b/src/main/java/org/teapot/backend/config/security/AuthorizationServerConfig.java index dbed0a2..1aa2390 100644 --- a/src/main/java/org/teapot/backend/config/security/AuthorizationServerConfig.java +++ b/src/main/java/org/teapot/backend/config/security/AuthorizationServerConfig.java @@ -2,7 +2,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; @@ -14,7 +13,6 @@ @Configuration @EnableAuthorizationServer -@Profile("security") public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired diff --git a/src/main/java/org/teapot/backend/config/security/DefaultProfileWebSecurityConfig.java b/src/main/java/org/teapot/backend/config/security/DefaultProfileWebSecurityConfig.java deleted file mode 100644 index 1b1ca49..0000000 --- a/src/main/java/org/teapot/backend/config/security/DefaultProfileWebSecurityConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.teapot.backend.config.security; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; - - -@Configuration -@EnableWebSecurity -@Profile("!security") -public class DefaultProfileWebSecurityConfig extends WebSecurityConfigurerAdapter { - - @Override - public void configure(WebSecurity web) throws Exception { - web.ignoring().antMatchers("/**"); - } -} diff --git a/src/main/java/org/teapot/backend/config/security/ResourceServerConfig.java b/src/main/java/org/teapot/backend/config/security/ResourceServerConfig.java index 733da74..c5d66ee 100644 --- a/src/main/java/org/teapot/backend/config/security/ResourceServerConfig.java +++ b/src/main/java/org/teapot/backend/config/security/ResourceServerConfig.java @@ -2,7 +2,6 @@ import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; @@ -12,7 +11,6 @@ @Configuration @EnableResourceServer -@Profile("security") @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER - 1) public class ResourceServerConfig extends ResourceServerConfigurerAdapter { diff --git a/src/main/java/org/teapot/backend/config/security/SecurityProfileWebSecurityConfig.java b/src/main/java/org/teapot/backend/config/security/WebSecurityConfig.java similarity index 85% rename from src/main/java/org/teapot/backend/config/security/SecurityProfileWebSecurityConfig.java rename to src/main/java/org/teapot/backend/config/security/WebSecurityConfig.java index ae5786b..1144073 100644 --- a/src/main/java/org/teapot/backend/config/security/SecurityProfileWebSecurityConfig.java +++ b/src/main/java/org/teapot/backend/config/security/WebSecurityConfig.java @@ -3,7 +3,6 @@ import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; @@ -15,11 +14,10 @@ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) -@Profile("security") @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) -public class SecurityProfileWebSecurityConfig extends WebSecurityConfigurerAdapter { +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - public SecurityProfileWebSecurityConfig() { + public WebSecurityConfig() { super(true); } diff --git a/src/main/java/org/teapot/backend/controller/TeapotActionController.java b/src/main/java/org/teapot/backend/controller/meta/TeapotActionController.java similarity index 88% rename from src/main/java/org/teapot/backend/controller/TeapotActionController.java rename to src/main/java/org/teapot/backend/controller/meta/TeapotActionController.java index f30bf07..b7ffde9 100644 --- a/src/main/java/org/teapot/backend/controller/TeapotActionController.java +++ b/src/main/java/org/teapot/backend/controller/meta/TeapotActionController.java @@ -1,4 +1,4 @@ -package org.teapot.backend.controller; +package org.teapot.backend.controller.meta; import com.google.common.primitives.Longs; import org.springframework.beans.factory.annotation.Autowired; @@ -7,13 +7,13 @@ import org.teapot.backend.controller.exception.BadRequestException; import org.teapot.backend.controller.exception.ConflictException; import org.teapot.backend.controller.exception.ResourceNotFoundException; -import org.teapot.backend.model.User; -import org.teapot.backend.model.VerificationToken; +import org.teapot.backend.model.user.User; +import org.teapot.backend.model.user.VerificationToken; import org.teapot.backend.model.meta.TeapotAction; -import org.teapot.backend.repository.TeapotActionRepository; -import org.teapot.backend.repository.TeapotResourceRepository; -import org.teapot.backend.repository.UserRepository; -import org.teapot.backend.repository.VerificationTokenRepository; +import org.teapot.backend.repository.meta.TeapotActionRepository; +import org.teapot.backend.repository.meta.TeapotResourceRepository; +import org.teapot.backend.repository.user.UserRepository; +import org.teapot.backend.repository.user.VerificationTokenRepository; import org.teapot.backend.util.VerificationMailSender; import java.time.LocalDateTime; diff --git a/src/main/java/org/teapot/backend/controller/TeapotPropertyController.java b/src/main/java/org/teapot/backend/controller/meta/TeapotPropertyController.java similarity index 95% rename from src/main/java/org/teapot/backend/controller/TeapotPropertyController.java rename to src/main/java/org/teapot/backend/controller/meta/TeapotPropertyController.java index 42ecd3a..51bc400 100644 --- a/src/main/java/org/teapot/backend/controller/TeapotPropertyController.java +++ b/src/main/java/org/teapot/backend/controller/meta/TeapotPropertyController.java @@ -1,4 +1,4 @@ -package org.teapot.backend.controller; +package org.teapot.backend.controller.meta; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; @@ -7,7 +7,7 @@ import org.teapot.backend.controller.exception.BadRequestException; import org.teapot.backend.controller.exception.ResourceNotFoundException; import org.teapot.backend.model.meta.TeapotProperty; -import org.teapot.backend.repository.TeapotPropertyRepository; +import org.teapot.backend.repository.meta.TeapotPropertyRepository; import javax.servlet.http.HttpServletResponse; import java.util.List; diff --git a/src/main/java/org/teapot/backend/controller/TeapotResourceController.java b/src/main/java/org/teapot/backend/controller/meta/TeapotResourceController.java similarity index 95% rename from src/main/java/org/teapot/backend/controller/TeapotResourceController.java rename to src/main/java/org/teapot/backend/controller/meta/TeapotResourceController.java index c9eb120..5f0d937 100644 --- a/src/main/java/org/teapot/backend/controller/TeapotResourceController.java +++ b/src/main/java/org/teapot/backend/controller/meta/TeapotResourceController.java @@ -1,4 +1,4 @@ -package org.teapot.backend.controller; +package org.teapot.backend.controller.meta; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; @@ -7,7 +7,7 @@ import org.teapot.backend.controller.exception.BadRequestException; import org.teapot.backend.controller.exception.ResourceNotFoundException; import org.teapot.backend.model.meta.TeapotResource; -import org.teapot.backend.repository.TeapotResourceRepository; +import org.teapot.backend.repository.meta.TeapotResourceRepository; import javax.servlet.http.HttpServletResponse; import java.util.List; diff --git a/src/main/java/org/teapot/backend/controller/organization/OrganizationController.java b/src/main/java/org/teapot/backend/controller/organization/OrganizationController.java new file mode 100644 index 0000000..8a2d1db --- /dev/null +++ b/src/main/java/org/teapot/backend/controller/organization/OrganizationController.java @@ -0,0 +1,443 @@ +package org.teapot.backend.controller.organization; + +import com.google.common.primitives.Longs; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import org.teapot.backend.controller.exception.BadRequestException; +import org.teapot.backend.controller.exception.ForbiddenException; +import org.teapot.backend.controller.exception.ResourceNotFoundException; +import org.teapot.backend.model.organization.Member; +import org.teapot.backend.model.organization.MemberStatus; +import org.teapot.backend.model.organization.Organization; +import org.teapot.backend.model.user.User; +import org.teapot.backend.model.user.UserAuthority; +import org.teapot.backend.repository.organization.MemberRepository; +import org.teapot.backend.repository.organization.OrganizationRepository; +import org.teapot.backend.repository.user.UserRepository; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Optional.ofNullable; + +@RestController +@RequestMapping("/organizations") +public class OrganizationController { + + @Autowired + private UserRepository userRepository; + + @Autowired + private OrganizationRepository organizationRepository; + + @Autowired + private MemberRepository memberRepository; + + private Organization findOrganizationByIdOrName(String idOrName) { + Long id = Longs.tryParse(idOrName); + return ofNullable((id != null) + ? organizationRepository.findOne(id) + : organizationRepository.findByName(idOrName)) + .orElseThrow(ResourceNotFoundException::new); + } + + /** + * GET /organizations + * ?[pageNumber(number)]&[pageSize(number)]&[offset(number)]&[sort(string)] + *

+ * Доступен всем пользователям. + * Возвращает список всех организаций (или не всех, при указании параметров в запросе). + *

+ * Возможные коды состояний: + * 200 OK + */ + @GetMapping + public List getOrganizations(Pageable pageable) { + return organizationRepository.findAll(pageable).getContent(); + } + + /** + * GET /organizations + * ?user(number|string)&[pageNumber(number)]&[pageSize(number)]&[offset(number)]&[sort(string)] + *

+ * Доступен всем пользователям + * Возвращает список организаций, в которых состоит пользователь с указанным id или username. + *

+ * Возможные коды состояний: + * 200 OK + * 404 Not Found + */ + @GetMapping(params = "user") + public List getOrganizationsOfUser( + @RequestParam("user") String idOrUsername, + Pageable pageable + ) { + Long id = Longs.tryParse(idOrUsername); + User user = ofNullable((id != null) + ? userRepository.findOne(id) + : userRepository.findByUsername(idOrUsername)) + .orElseThrow(ResourceNotFoundException::new); + + return memberRepository.findByUser(user, pageable) + .stream() + .map(Member::getOrganization) + .collect(Collectors.toList()); + } + + /** + * GET /organizations/{idOrName} + *

+ * Доступен всем пользователям. + * Возвращает организацию с указанным id или name. + *

+ * Возможные коды состояний: + * 200 OK + * 404 Not Found + */ + @GetMapping("/{idOrName:.+}") + public Organization getOrganization( + @PathVariable String idOrName + ) { + Organization organization = findOrganizationByIdOrName(idOrName); + if (organization == null) { + throw new ResourceNotFoundException(); + } + + return organization; + } + + /** + * DELETE /organizations/{id} + *

+ * Доступен администраторам или создателю организации. + * Удаляет организацию с указанным id. + *

+ * Возможные коды состояний: + * 204 No Content + * 401 Unauthorized + * 403 Forbidden + * 404 Not Found + */ + @PreAuthorize("isAuthenticated()") + @DeleteMapping("/{organizationId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteOrganization( + @PathVariable Long organizationId, + Authentication auth + ) { + Organization organization = organizationRepository + .findOne(organizationId); + if (organization == null) { + throw new ResourceNotFoundException(); + } + + Member authMember = memberRepository + .findByOrganizationAndUser(organization, + userRepository.findByEmail(auth.getName())); + + if ((auth.getAuthorities().contains(UserAuthority.ADMIN)) + || (authMember != null) && (authMember.getStatus().equals(MemberStatus.CREATOR))) { + organizationRepository.delete(organization); + } else { + throw new ForbiddenException(); + } + } + + /** + * POST /organizations + * Тело запроса: + * { + * "name": "название", + * "fillName": "полное название" (не обязательно) + * } + *

+ * Метод доступен всем авторизованным пользователям. + * Создает новую организацию с указанными данными. Устаналивает пользователя, выполнившего запрос, создателем + * организации. Устаналивает заголовок Location и возвращает созданную организацию. + *

+ * Возможные коды состояний: + * 201 Created + * 400 Bad Request + * 401 Unauthorized + */ + @PreAuthorize("isAuthenticated()") + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Organization createOrganization( + @RequestBody Organization organization, + HttpServletResponse response, + Authentication auth + ) { + if (organizationRepository.findByName(organization.getName()) != null) { + throw new BadRequestException(); + } + + organization.setCreationDate(LocalDate.now()); + organizationRepository.saveAndFlush(organization); + + Member creator = new Member(); + creator.setAdmissionDate(LocalDate.now()); + creator.setOrganization(organization); + creator.setStatus(MemberStatus.CREATOR); + creator.setUser(userRepository.findByEmail(auth.getName())); + memberRepository.save(creator); + organization.getMembers().add(creator); + + response.setHeader(HttpHeaders.LOCATION, + "/organizations/" + organization.getId()); + return organization; + } + + /** + * PATCH /organizations/{id} + * ?[name(string)]&[fullName(string)] + *

+ * Доступен администраторам, создателю организации и владельцам организации. + * Изменяет name и fullName организации, если соответсвующие параметры указаны. + *

+ * Возможные коды состояний: + * 204 No Content + * 401 Unauthorized + * 403 Forbidden + * 404 Not Found + */ + @PreAuthorize("isAuthenticated()") + @PatchMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void patchOrganization( + @PathVariable Long id, + @RequestParam(required = false) String name, + @RequestParam(required = false) String fullName, + Authentication auth + ) { + Organization organization = organizationRepository.findOne(id); + if (organization == null) { + throw new ResourceNotFoundException(); + } + + Member authMember = memberRepository.findByOrganizationAndUser(organization, + userRepository.findByEmail(auth.getName())); + + if ((auth.getAuthorities().contains(UserAuthority.ADMIN)) + || (authMember != null) && (authMember.getStatus().equals(MemberStatus.CREATOR)) + || (authMember != null) && (authMember.getStatus().equals(MemberStatus.OWNER))) { + + if (name != null) { + organization.setName(name); + } + if (fullName != null) { + organization.setFullName(fullName); + } + organizationRepository.save(organization); + + } else { + throw new ForbiddenException(); + } + } + + /** + * GET /organizations/{organizationId}/members + * ?[pageNumber(number)]&[pageSize(number)]&[offset(number)]&[sort(string)] + *

+ * Доступен всем пользователям. + * Возвращает список всех участников организации (или не всех, при указании параметров в запросе). + *

+ * Возможные коды состояний: + * 200 OK + * 404 Not Found + */ + @GetMapping("/{organizationIdOrName}/members") + public List getOrganizationMembers( + @PathVariable String organizationIdOrName, + Pageable pageable + ) { + Organization organization = ofNullable( + findOrganizationByIdOrName(organizationIdOrName)) + .orElseThrow(ResourceNotFoundException::new); + + return memberRepository + .findAllByOrganization(organization, pageable) + .getContent(); + } + + /** + * GET /organizations/{organizationId}/members/{memberId} + *

+ *

+ * Доступен администраторам, создателю и владельцам организации. + * Возвращает участника memberId организации organizationId. + *

+ * Возможные коды состояний: + * 200 OK + * 404 Not Found + */ + @GetMapping("/{organizationIdOrName}/members/{memberId}") + public Member getOrganizationMember( + @PathVariable String organizationIdOrName, + @PathVariable Long memberId, + Authentication auth + ) { + + Organization organization = ofNullable( + findOrganizationByIdOrName(organizationIdOrName)) + .orElseThrow(ResourceNotFoundException::new); + + return ofNullable(memberRepository + .findByOrganizationAndId(organization, memberId)) + .orElseThrow(ResourceNotFoundException::new); + } + + /** + * POST /organizations/{organizationId}/members + * Тело запроса: + * { + * "status": "статус", + * "userId": 123 + * } + *

+ * Доступен только администраторам. + * Добавляет в организацию участника с полученными данными. Нельзя добавить участника со статусом создателя, так + * как создатель может быть только один. + *

+ * Возможные коды состояний: + * 201 Created + * 400 Bad Request + * 401 Unauthorized + * 403 Forbidden + * 404 Not Found + */ + // todo: приглашение нового участника + @PreAuthorize("hasRole('ADMIN')") + @PostMapping("/{organizationId}/members") + @ResponseStatus(HttpStatus.CREATED) + public Member addMember( + @PathVariable Long organizationId, + @RequestBody Member member, + HttpServletResponse response + ) { + Organization organization = ofNullable( + organizationRepository.findOne(organizationId)) + .orElseThrow(ResourceNotFoundException::new); + + if (memberRepository.findByOrganizationAndUser(organization, + member.getUser()) != null) { + throw new BadRequestException(); + } + + member.setAdmissionDate(LocalDate.now()); + member.setOrganization(organization); + if (member.getStatus().equals(MemberStatus.CREATOR)) { + // нельзя добавить нового создателя организации + member.setStatus(MemberStatus.OWNER); + } + memberRepository.save(member); + + response.setHeader(HttpHeaders.LOCATION, + String.format("/organizations/%d/members/%d", + organizationId, member.getId())); + return member; + } + + /** + * PATCH /organizations/{organizationId}/members/{memberId} + * ?status(string) + *

+ * Доступен администраторам, создателю и владельцам организации. + * Изменяет статус участника. Нельзя изменить статус создателя и статус другого участника на статус создателя. + *

+ * Возможные коды состояний: + * 204 No Content + * 401 Unauthorized + * 403 Forbidden + * 404 Not Found + */ + @PreAuthorize("isAuthenticated()") + @PatchMapping("/{organizationId}/members/{memberId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void patchMember( + @PathVariable Long organizationId, + @PathVariable Long memberId, + @RequestParam("status") MemberStatus newStatus, + Authentication auth + ) { + Organization organization = ofNullable( + organizationRepository.findOne(organizationId)) + .orElseThrow(ResourceNotFoundException::new); + + Member member = ofNullable(memberRepository + .findByOrganizationAndId(organization, memberId)) + .orElseThrow(ResourceNotFoundException::new); + + Member authMember = memberRepository + .findByOrganizationAndUser(organization, + userRepository.findByEmail(auth.getName())); + + if ((auth.getAuthorities().contains(UserAuthority.ADMIN)) + || (authMember != null) && (authMember.getStatus().equals(MemberStatus.CREATOR)) + || (authMember != null) && (authMember.getStatus().equals(MemberStatus.OWNER))) { + + if (!member.getStatus().equals(MemberStatus.CREATOR) + && !newStatus.equals(MemberStatus.CREATOR)) { + + member.setStatus(newStatus); + memberRepository.save(member); + return; + } + } + throw new ForbiddenException(); + } + + /** + * DELETE /organizations/{organizationId}/members/{memberId} + *

+ * Доступен администраторам, создателю и владельцам организации. + * Удаляет участника memberId из организации organizationId. + *

+ * Возможные коды состояний: + * 204 No Content + * 401 Unauthorized + * 403 Forbidden + * 404 Not Found + */ + @PreAuthorize("isAuthenticated()") + @DeleteMapping("/{organizationId}/members/{memberId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteMember( + @PathVariable Long organizationId, + @PathVariable Long memberId, + Authentication auth + ) { + Organization organization = ofNullable( + organizationRepository.findOne(organizationId)) + .orElseThrow(ResourceNotFoundException::new); + + Member member = ofNullable(memberRepository + .findByOrganizationAndId(organization, memberId)) + .orElseThrow(ResourceNotFoundException::new); + + Member authMember = memberRepository + .findByOrganizationAndUser(organization, + userRepository.findByEmail(auth.getName())); + + if (auth.getAuthorities().contains(UserAuthority.ADMIN)) { + memberRepository.delete(member); + return; + + } else if ((authMember != null) && (authMember.getStatus().equals(MemberStatus.CREATOR)) + || (authMember != null) && (authMember.getStatus().equals(MemberStatus.OWNER))) { + + if (!member.getStatus().equals(MemberStatus.CREATOR)) { + memberRepository.delete(member); + return; + } + } + throw new ForbiddenException(); + } +} diff --git a/src/main/java/org/teapot/backend/controller/UserController.java b/src/main/java/org/teapot/backend/controller/user/UserController.java similarity index 98% rename from src/main/java/org/teapot/backend/controller/UserController.java rename to src/main/java/org/teapot/backend/controller/user/UserController.java index 536963f..1971b3c 100644 --- a/src/main/java/org/teapot/backend/controller/UserController.java +++ b/src/main/java/org/teapot/backend/controller/user/UserController.java @@ -1,4 +1,4 @@ -package org.teapot.backend.controller; +package org.teapot.backend.controller.user; import com.google.common.primitives.Longs; import org.springframework.beans.factory.annotation.Autowired; @@ -13,14 +13,13 @@ import org.teapot.backend.controller.exception.BadRequestException; import org.teapot.backend.controller.exception.ForbiddenException; import org.teapot.backend.controller.exception.ResourceNotFoundException; -import org.teapot.backend.model.User; -import org.teapot.backend.model.UserAuthority; -import org.teapot.backend.repository.UserRepository; +import org.teapot.backend.model.user.User; +import org.teapot.backend.model.user.UserAuthority; +import org.teapot.backend.repository.user.UserRepository; import org.teapot.backend.util.VerificationMailSender; import javax.servlet.http.HttpServletResponse; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; @@ -173,7 +172,7 @@ public User registerUser(@RequestBody User user, throw new BadRequestException(); } - user.setRegistrationDate(LocalDateTime.now()); + user.setRegistrationDate(LocalDate.now()); user.setPassword(passwordEncoder.encode(user.getPassword())); if (auth == null) { diff --git a/src/main/java/org/teapot/backend/model/organization/Member.java b/src/main/java/org/teapot/backend/model/organization/Member.java new file mode 100644 index 0000000..58e538f --- /dev/null +++ b/src/main/java/org/teapot/backend/model/organization/Member.java @@ -0,0 +1,92 @@ +package org.teapot.backend.model.organization; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import org.teapot.backend.model.user.User; +import org.teapot.backend.util.deser.MemberDeserializer; +import org.teapot.backend.util.ser.MemberSerializer; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDate; + +@Entity +@Table(name = "organization_member") +@JsonSerialize(using = MemberSerializer.class) +@JsonDeserialize(using = MemberDeserializer.class) +public class Member implements Serializable { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne(optional = false) + @OnDelete(action = OnDeleteAction.CASCADE) + private User user; + + @Enumerated + private MemberStatus status; + + @ManyToOne(fetch = FetchType.LAZY) + private Organization organization; + + @Column(name = "admission_date") + private LocalDate admissionDate; + + public Member() { + } + + public Member(Long id, + User user, + MemberStatus status, + Organization organization, + LocalDate admissionDate) { + this.id = id; + this.user = user; + this.status = status; + this.organization = organization; + this.admissionDate = admissionDate; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public MemberStatus getStatus() { + return status; + } + + public void setStatus(MemberStatus status) { + this.status = status; + } + + public Organization getOrganization() { + return organization; + } + + public void setOrganization(Organization organization) { + this.organization = organization; + } + + public LocalDate getAdmissionDate() { + return admissionDate; + } + + public void setAdmissionDate(LocalDate admissionDate) { + this.admissionDate = admissionDate; + } +} diff --git a/src/main/java/org/teapot/backend/model/organization/MemberStatus.java b/src/main/java/org/teapot/backend/model/organization/MemberStatus.java new file mode 100644 index 0000000..dcdfb87 --- /dev/null +++ b/src/main/java/org/teapot/backend/model/organization/MemberStatus.java @@ -0,0 +1,8 @@ +package org.teapot.backend.model.organization; + +public enum MemberStatus { + CREATOR, + OWNER, + WORKER, + APPLICANT; +} diff --git a/src/main/java/org/teapot/backend/model/organization/Organization.java b/src/main/java/org/teapot/backend/model/organization/Organization.java new file mode 100644 index 0000000..c6e7549 --- /dev/null +++ b/src/main/java/org/teapot/backend/model/organization/Organization.java @@ -0,0 +1,115 @@ +package org.teapot.backend.model.organization; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.teapot.backend.util.ser.OrganizationSerializer; + +import javax.persistence.*; +import java.time.LocalDate; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Entity +@Table(name = "organization") +@JsonSerialize(using = OrganizationSerializer.class) +public class Organization { + + @Id + @GeneratedValue + private Long id; + + @Column(unique = true, nullable = false) + private String name; + + @Column(name = "full_name") + private String fullName; + + @Column(name = "creation_date") + private LocalDate creationDate; + + @OneToMany(mappedBy = "organization", fetch = FetchType.LAZY, + cascade = CascadeType.ALL, orphanRemoval = true) + private Set members = new HashSet<>(); + + public Organization() { + } + + public Organization(Long id, + String name, + String fullName, + LocalDate creationDate, + Set members) { + this.id = id; + this.name = name; + this.fullName = fullName; + this.creationDate = creationDate; + this.members = members; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public LocalDate getCreationDate() { + return creationDate; + } + + public void setCreationDate(LocalDate creationDate) { + this.creationDate = creationDate; + } + + public Set getMembers() { + return members; + } + + public void setMembers(Set members) { + this.members = members; + } + + @Override + public int hashCode() { + return Objects.hash( + id, + name, + fullName, + creationDate, + members + ); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final Organization other = (Organization) obj; + return Objects.equals(this.id, other.id) + && Objects.equals(this.name, other.name) + && Objects.equals(this.fullName, other.fullName) + && Objects.equals(this.creationDate, other.creationDate) + && Objects.equals(this.members, other.members); + } +} diff --git a/src/main/java/org/teapot/backend/model/User.java b/src/main/java/org/teapot/backend/model/user/User.java similarity index 74% rename from src/main/java/org/teapot/backend/model/User.java rename to src/main/java/org/teapot/backend/model/user/User.java index 732fed8..4a084af 100644 --- a/src/main/java/org/teapot/backend/model/User.java +++ b/src/main/java/org/teapot/backend/model/user/User.java @@ -1,15 +1,17 @@ -package org.teapot.backend.model; +package org.teapot.backend.model.user; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.teapot.backend.util.ser.UserSerializer; import javax.persistence.*; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.Objects; @Entity @Table(name = "user") +@JsonSerialize(using = UserSerializer.class) public class User { @Id @@ -41,19 +43,46 @@ public class User { private UserAuthority authority = UserAuthority.USER; @Column(name = "registration_date") - private LocalDateTime registrationDate; + private LocalDate registrationDate; private LocalDate birthday; private String description; - @JsonIgnore @OneToOne(mappedBy = "user", fetch = FetchType.LAZY, orphanRemoval = true) private VerificationToken verificationToken; public User() { } + public User(Long id, + String username, + String email, + String password, + Boolean isAvailable, + Boolean isActivated, + String firstName, + String lastName, + UserAuthority authority, + LocalDate registrationDate, + LocalDate birthday, + String description, + VerificationToken verificationToken) { + this.id = id; + this.username = username; + this.email = email; + this.password = password; + this.isAvailable = isAvailable; + this.isActivated = isActivated; + this.firstName = firstName; + this.lastName = lastName; + this.authority = authority; + this.registrationDate = registrationDate; + this.birthday = birthday; + this.description = description; + this.verificationToken = verificationToken; + } + public Long getId() { return id; } @@ -126,11 +155,11 @@ public void setAuthority(UserAuthority authority) { this.authority = authority; } - public LocalDateTime getRegistrationDate() { + public LocalDate getRegistrationDate() { return registrationDate; } - public void setRegistrationDate(LocalDateTime registrationDate) { + public void setRegistrationDate(LocalDate registrationDate) { this.registrationDate = registrationDate; } @@ -166,12 +195,14 @@ public int hashCode() { email, password, isAvailable, + isActivated, firstName, lastName, authority, registrationDate, birthday, - description + description, + verificationToken ); } @@ -189,11 +220,13 @@ public boolean equals(Object obj) { && Objects.equals(this.email, other.email) && Objects.equals(this.password, other.password) && Objects.equals(this.isAvailable, other.isAvailable) + && Objects.equals(this.isActivated, other.isActivated) && Objects.equals(this.firstName, other.firstName) && Objects.equals(this.lastName, other.lastName) && Objects.equals(this.authority, other.authority) && Objects.equals(this.registrationDate, other.registrationDate) && Objects.equals(this.birthday, other.birthday) - && Objects.equals(this.description, other.description); + && Objects.equals(this.description, other.description) + && Objects.equals(this.verificationToken, other.verificationToken); } } diff --git a/src/main/java/org/teapot/backend/model/UserAuthority.java b/src/main/java/org/teapot/backend/model/user/UserAuthority.java similarity index 85% rename from src/main/java/org/teapot/backend/model/UserAuthority.java rename to src/main/java/org/teapot/backend/model/user/UserAuthority.java index 36ffcd9..10172eb 100644 --- a/src/main/java/org/teapot/backend/model/UserAuthority.java +++ b/src/main/java/org/teapot/backend/model/user/UserAuthority.java @@ -1,4 +1,4 @@ -package org.teapot.backend.model; +package org.teapot.backend.model.user; import org.springframework.security.core.GrantedAuthority; diff --git a/src/main/java/org/teapot/backend/model/VerificationToken.java b/src/main/java/org/teapot/backend/model/user/VerificationToken.java similarity index 96% rename from src/main/java/org/teapot/backend/model/VerificationToken.java rename to src/main/java/org/teapot/backend/model/user/VerificationToken.java index 4ab6033..d2b1405 100644 --- a/src/main/java/org/teapot/backend/model/VerificationToken.java +++ b/src/main/java/org/teapot/backend/model/user/VerificationToken.java @@ -1,4 +1,4 @@ -package org.teapot.backend.model; +package org.teapot.backend.model.user; import javax.persistence.*; import java.time.LocalDateTime; diff --git a/src/main/java/org/teapot/backend/repository/TeapotActionRepository.java b/src/main/java/org/teapot/backend/repository/meta/TeapotActionRepository.java similarity index 89% rename from src/main/java/org/teapot/backend/repository/TeapotActionRepository.java rename to src/main/java/org/teapot/backend/repository/meta/TeapotActionRepository.java index a3a5514..5aa9dfa 100644 --- a/src/main/java/org/teapot/backend/repository/TeapotActionRepository.java +++ b/src/main/java/org/teapot/backend/repository/meta/TeapotActionRepository.java @@ -1,4 +1,4 @@ -package org.teapot.backend.repository; +package org.teapot.backend.repository.meta; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/org/teapot/backend/repository/TeapotPropertyRepository.java b/src/main/java/org/teapot/backend/repository/meta/TeapotPropertyRepository.java similarity index 89% rename from src/main/java/org/teapot/backend/repository/TeapotPropertyRepository.java rename to src/main/java/org/teapot/backend/repository/meta/TeapotPropertyRepository.java index e89ac35..f1b6a32 100644 --- a/src/main/java/org/teapot/backend/repository/TeapotPropertyRepository.java +++ b/src/main/java/org/teapot/backend/repository/meta/TeapotPropertyRepository.java @@ -1,4 +1,4 @@ -package org.teapot.backend.repository; +package org.teapot.backend.repository.meta; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/org/teapot/backend/repository/TeapotResourceRepository.java b/src/main/java/org/teapot/backend/repository/meta/TeapotResourceRepository.java similarity index 85% rename from src/main/java/org/teapot/backend/repository/TeapotResourceRepository.java rename to src/main/java/org/teapot/backend/repository/meta/TeapotResourceRepository.java index e9ef975..03774a4 100644 --- a/src/main/java/org/teapot/backend/repository/TeapotResourceRepository.java +++ b/src/main/java/org/teapot/backend/repository/meta/TeapotResourceRepository.java @@ -1,4 +1,4 @@ -package org.teapot.backend.repository; +package org.teapot.backend.repository.meta; import org.springframework.data.jpa.repository.JpaRepository; import org.teapot.backend.model.meta.TeapotResource; diff --git a/src/main/java/org/teapot/backend/repository/organization/MemberRepository.java b/src/main/java/org/teapot/backend/repository/organization/MemberRepository.java new file mode 100644 index 0000000..fa4263f --- /dev/null +++ b/src/main/java/org/teapot/backend/repository/organization/MemberRepository.java @@ -0,0 +1,25 @@ +package org.teapot.backend.repository.organization; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.teapot.backend.model.organization.Member; +import org.teapot.backend.model.organization.Organization; +import org.teapot.backend.model.user.User; + +import java.util.List; + +public interface MemberRepository extends JpaRepository { + + Page findAllByOrganization(Organization organization, Pageable pageable); + + List findAllByOrganization(Organization organization); + + Member findByOrganizationAndId(Organization organization, Long id); + + Member findByOrganizationAndUser(Organization organization, User user); + + List findByUser(User user); + + List findByUser(User user, Pageable pageable); +} diff --git a/src/main/java/org/teapot/backend/repository/organization/OrganizationRepository.java b/src/main/java/org/teapot/backend/repository/organization/OrganizationRepository.java new file mode 100644 index 0000000..114a2d6 --- /dev/null +++ b/src/main/java/org/teapot/backend/repository/organization/OrganizationRepository.java @@ -0,0 +1,9 @@ +package org.teapot.backend.repository.organization; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.teapot.backend.model.organization.Organization; + +public interface OrganizationRepository extends JpaRepository { + + Organization findByName(String name); +} diff --git a/src/main/java/org/teapot/backend/repository/UserRepository.java b/src/main/java/org/teapot/backend/repository/user/UserRepository.java similarity index 75% rename from src/main/java/org/teapot/backend/repository/UserRepository.java rename to src/main/java/org/teapot/backend/repository/user/UserRepository.java index 6f30768..3ddfa7f 100644 --- a/src/main/java/org/teapot/backend/repository/UserRepository.java +++ b/src/main/java/org/teapot/backend/repository/user/UserRepository.java @@ -1,7 +1,7 @@ -package org.teapot.backend.repository; +package org.teapot.backend.repository.user; import org.springframework.data.jpa.repository.JpaRepository; -import org.teapot.backend.model.User; +import org.teapot.backend.model.user.User; import javax.transaction.Transactional; diff --git a/src/main/java/org/teapot/backend/repository/VerificationTokenRepository.java b/src/main/java/org/teapot/backend/repository/user/VerificationTokenRepository.java similarity index 74% rename from src/main/java/org/teapot/backend/repository/VerificationTokenRepository.java rename to src/main/java/org/teapot/backend/repository/user/VerificationTokenRepository.java index 7ae77c2..73077b4 100644 --- a/src/main/java/org/teapot/backend/repository/VerificationTokenRepository.java +++ b/src/main/java/org/teapot/backend/repository/user/VerificationTokenRepository.java @@ -1,8 +1,8 @@ -package org.teapot.backend.repository; +package org.teapot.backend.repository.user; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.transaction.annotation.Transactional; -import org.teapot.backend.model.VerificationToken; +import org.teapot.backend.model.user.VerificationToken; @Transactional public interface VerificationTokenRepository extends JpaRepository { diff --git a/src/main/java/org/teapot/backend/service/UserDetailsServiceImpl.java b/src/main/java/org/teapot/backend/service/UserDetailsServiceImpl.java index 0374399..ea770a3 100644 --- a/src/main/java/org/teapot/backend/service/UserDetailsServiceImpl.java +++ b/src/main/java/org/teapot/backend/service/UserDetailsServiceImpl.java @@ -1,19 +1,16 @@ package org.teapot.backend.service; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Profile; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import org.teapot.backend.model.User; -import org.teapot.backend.repository.UserRepository; +import org.teapot.backend.model.user.User; +import org.teapot.backend.repository.user.UserRepository; import java.util.Collections; - @Service -@Profile("security") public class UserDetailsServiceImpl implements UserDetailsService { @Autowired diff --git a/src/main/java/org/teapot/backend/util/LinkBuilder.java b/src/main/java/org/teapot/backend/util/LinkBuilder.java new file mode 100644 index 0000000..1fc045c --- /dev/null +++ b/src/main/java/org/teapot/backend/util/LinkBuilder.java @@ -0,0 +1,24 @@ +package org.teapot.backend.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.teapot.backend.repository.meta.TeapotPropertyRepository; + +import java.util.Arrays; +import java.util.Objects; + +@Component +public class LinkBuilder { + + @Autowired + private TeapotPropertyRepository propertyRepository; + + public String format(String relativePathFormat, Object... args) { + if (Arrays.stream(args).anyMatch(Objects::isNull)) { + return null; + } else { + return propertyRepository.findByName("site-uri").getValue() + + String.format(relativePathFormat, args); + } + } +} diff --git a/src/main/java/org/teapot/backend/util/VerificationMailSender.java b/src/main/java/org/teapot/backend/util/VerificationMailSender.java index bdff934..a8a2148 100644 --- a/src/main/java/org/teapot/backend/util/VerificationMailSender.java +++ b/src/main/java/org/teapot/backend/util/VerificationMailSender.java @@ -5,10 +5,10 @@ import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Component; -import org.teapot.backend.model.User; -import org.teapot.backend.model.VerificationToken; -import org.teapot.backend.repository.TeapotPropertyRepository; -import org.teapot.backend.repository.VerificationTokenRepository; +import org.teapot.backend.model.user.User; +import org.teapot.backend.model.user.VerificationToken; +import org.teapot.backend.repository.meta.TeapotPropertyRepository; +import org.teapot.backend.repository.user.VerificationTokenRepository; import java.util.Locale; diff --git a/src/main/java/org/teapot/backend/util/VerificationTokenGenerator.java b/src/main/java/org/teapot/backend/util/VerificationTokenGenerator.java index db858f4..b77a693 100644 --- a/src/main/java/org/teapot/backend/util/VerificationTokenGenerator.java +++ b/src/main/java/org/teapot/backend/util/VerificationTokenGenerator.java @@ -2,9 +2,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.teapot.backend.model.VerificationToken; -import org.teapot.backend.repository.TeapotPropertyRepository; -import org.teapot.backend.repository.VerificationTokenRepository; +import org.teapot.backend.model.user.VerificationToken; +import org.teapot.backend.repository.meta.TeapotPropertyRepository; +import org.teapot.backend.repository.user.VerificationTokenRepository; import java.time.LocalDateTime; diff --git a/src/main/java/org/teapot/backend/util/deser/MemberDeserializer.java b/src/main/java/org/teapot/backend/util/deser/MemberDeserializer.java new file mode 100644 index 0000000..571f2e5 --- /dev/null +++ b/src/main/java/org/teapot/backend/util/deser/MemberDeserializer.java @@ -0,0 +1,69 @@ +package org.teapot.backend.util.deser; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ValueNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.teapot.backend.model.organization.Member; +import org.teapot.backend.model.organization.MemberStatus; +import org.teapot.backend.model.organization.Organization; +import org.teapot.backend.model.user.User; +import org.teapot.backend.repository.organization.OrganizationRepository; +import org.teapot.backend.repository.user.UserRepository; + +import java.io.IOException; + +@Component +public class MemberDeserializer extends StdDeserializer { + + @Autowired + private UserRepository userRepository; + + @Autowired + private OrganizationRepository organizationRepository; + + public MemberDeserializer() { + this(null); + } + + private MemberDeserializer(Class vc) { + super(vc); + } + + @Override + public Member deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + TreeNode node = p.getCodec().readTree(p); + + ValueNode idNode = (ValueNode) node.get("id"); + Long id = (idNode instanceof NullNode) || (idNode == null) + ? null + : idNode.asLong(); + + ValueNode userIdNode = (ValueNode) node.get("userId"); + User user = (userIdNode instanceof NullNode) || (userIdNode == null) + ? null + : userRepository.findOne(userIdNode.asLong()); + + ValueNode statusNode = (ValueNode) node.get("status"); + MemberStatus status = (statusNode instanceof NullNode) || (statusNode == null) + ? null + : MemberStatus.valueOf(statusNode.asText()); + + ValueNode organizationIdNode = (ValueNode) node.get("organizationId"); + Organization organization = (organizationIdNode instanceof NullNode) || (organizationIdNode == null) + ? null + : organizationRepository.findOne(organizationIdNode.asLong()); + + return new Member( + id, + user, + status, + organization, + null + ); + } +} diff --git a/src/main/java/org/teapot/backend/util/ser/MemberSerializer.java b/src/main/java/org/teapot/backend/util/ser/MemberSerializer.java new file mode 100644 index 0000000..4a8868c --- /dev/null +++ b/src/main/java/org/teapot/backend/util/ser/MemberSerializer.java @@ -0,0 +1,48 @@ +package org.teapot.backend.util.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.teapot.backend.model.organization.Member; +import org.teapot.backend.util.LinkBuilder; + +import java.io.IOException; + +@Component +public class MemberSerializer extends StdSerializer { + + @Autowired + private LinkBuilder linkBuilder; + + public MemberSerializer() { + this(null); + } + + private MemberSerializer(Class t) { + super(t); + } + + @Override + public void serialize(Member member, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeStartObject(); + gen.writeObjectField("id", member.getId()); + if (member.getUser() == null) { + gen.writeNullField("user"); + } else { + gen.writeStringField("user", + linkBuilder.format("/users/%d", member.getUser().getId())); + } + gen.writeObjectField("status", member.getStatus()); + if (member.getOrganization() == null) { + gen.writeNullField("organization"); + } else { + gen.writeStringField("organization", + linkBuilder.format("/organizations/%d", member.getOrganization().getId())); + } + gen.writeObjectField("admissionDate", member.getAdmissionDate()); + gen.writeEndObject(); + } +} diff --git a/src/main/java/org/teapot/backend/util/ser/OrganizationSerializer.java b/src/main/java/org/teapot/backend/util/ser/OrganizationSerializer.java new file mode 100644 index 0000000..7e5f7fd --- /dev/null +++ b/src/main/java/org/teapot/backend/util/ser/OrganizationSerializer.java @@ -0,0 +1,39 @@ +package org.teapot.backend.util.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.teapot.backend.model.organization.Organization; +import org.teapot.backend.util.LinkBuilder; + +import java.io.IOException; + +@Component +public class OrganizationSerializer extends StdSerializer { + + @Autowired + private LinkBuilder linkBuilder; + + public OrganizationSerializer() { + this(null); + } + + private OrganizationSerializer(Class t) { + super(t); + } + + @Override + public void serialize(Organization org, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeStartObject(); + gen.writeObjectField("id", org.getId()); + gen.writeObjectField("name", org.getName()); + gen.writeObjectField("fullName", org.getFullName()); + gen.writeObjectField("creationDate", org.getCreationDate()); + gen.writeStringField("members", + linkBuilder.format("/organizations/%d/members", org.getId())); + gen.writeEndObject(); + } +} diff --git a/src/main/java/org/teapot/backend/util/ser/UserSerializer.java b/src/main/java/org/teapot/backend/util/ser/UserSerializer.java new file mode 100644 index 0000000..6f7fcd0 --- /dev/null +++ b/src/main/java/org/teapot/backend/util/ser/UserSerializer.java @@ -0,0 +1,46 @@ +package org.teapot.backend.util.ser; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.teapot.backend.model.user.User; +import org.teapot.backend.util.LinkBuilder; + +import java.io.IOException; + +@Component +public class UserSerializer extends StdSerializer { + + @Autowired + private LinkBuilder linkBuilder; + + public UserSerializer() { + this(null); + } + + private UserSerializer(Class t) { + super(t); + } + + @Override + public void serialize(User user, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeStartObject(); + gen.writeObjectField("id", user.getId()); + gen.writeObjectField("username", user.getUsername()); + gen.writeObjectField("email", user.getEmail()); + gen.writeObjectField("isAvailable", user.isAvailable()); + gen.writeObjectField("isActivated", user.isActivated()); + gen.writeObjectField("firstName", user.getFirstName()); + gen.writeObjectField("lastName", user.getLastName()); + gen.writeObjectField("authority", user.getAuthority()); + gen.writeObjectField("registrationDate", user.getRegistrationDate()); + gen.writeObjectField("birthday", user.getBirthday()); + gen.writeObjectField("description", user.getDescription()); + gen.writeObjectField("organizations", + linkBuilder.format("/organizations?user=%d", user.getId())); + gen.writeEndObject(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index bdb6b3c..eb45dd5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,3 @@ spring: profiles: - active: development,security + active: development diff --git a/src/test/java/org/teapot/backend/test/controller/AbstractControllerIT.java b/src/test/java/org/teapot/backend/test/controller/AbstractControllerIT.java index fd0a976..faaf71e 100644 --- a/src/test/java/org/teapot/backend/test/controller/AbstractControllerIT.java +++ b/src/test/java/org/teapot/backend/test/controller/AbstractControllerIT.java @@ -1,14 +1,13 @@ package org.teapot.backend.test.controller; -import org.junit.Assert; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.json.JacksonJsonParser; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.mock.http.MockHttpOutputMessage; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.web.servlet.MockMvc; @@ -16,14 +15,13 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; -import org.teapot.backend.model.User; -import org.teapot.backend.model.UserAuthority; -import org.teapot.backend.repository.UserRepository; +import org.teapot.backend.model.user.User; +import org.teapot.backend.model.user.UserAuthority; +import org.teapot.backend.repository.user.UserRepository; import org.teapot.backend.test.AbstractIT; import java.io.IOException; import java.nio.charset.Charset; -import java.util.Arrays; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -45,7 +43,7 @@ public abstract class AbstractControllerIT extends AbstractIT { protected JacksonJsonParser parser = new JacksonJsonParser(); - private HttpMessageConverter mappingJackson2HttpMessageConverter; + protected ObjectMapper mapper; @Autowired protected UserRepository userRepository; @@ -60,30 +58,23 @@ public abstract class AbstractControllerIT extends AbstractIT { protected String userAccessToken; @Autowired - private void setConverters(HttpMessageConverter[] converters) { - mappingJackson2HttpMessageConverter = Arrays.stream(converters) - .filter(converter -> converter instanceof MappingJackson2HttpMessageConverter) - .findAny().get(); - Assert.assertNotNull(mappingJackson2HttpMessageConverter); - } - - @Autowired - public void setMockMvc(WebApplicationContext wac, FilterChainProxy filter) throws Exception { + public void setMockMvcAndMapper(WebApplicationContext wac, FilterChainProxy filter) throws Exception { mockMvc = MockMvcBuilders .webAppContextSetup(wac) .addFilters(filter) .build(); + + mapper = Jackson2ObjectMapperBuilder.json() + .featuresToDisable(MapperFeature.USE_ANNOTATIONS) + .build(); } @SuppressWarnings("unchecked") protected String json(Object object) throws IOException { - MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage(); - mappingJackson2HttpMessageConverter.write( - object, MediaType.APPLICATION_JSON, mockHttpOutputMessage); - return mockHttpOutputMessage.getBodyAsString(); + return mapper.writeValueAsString(object); } - private String obtainAccessToken(String email, String password) throws Exception { + protected String obtainAccessToken(String email, String password) throws Exception { MultiValueMap params = new LinkedMultiValueMap<>(); params.add("grant_type", "password"); params.add("username", email); diff --git a/src/test/java/org/teapot/backend/test/controller/OrganizationControllerIT.java b/src/test/java/org/teapot/backend/test/controller/OrganizationControllerIT.java new file mode 100644 index 0000000..7387ba6 --- /dev/null +++ b/src/test/java/org/teapot/backend/test/controller/OrganizationControllerIT.java @@ -0,0 +1,605 @@ +package org.teapot.backend.test.controller; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.teapot.backend.model.organization.Member; +import org.teapot.backend.model.organization.MemberStatus; +import org.teapot.backend.model.organization.Organization; +import org.teapot.backend.model.user.User; +import org.teapot.backend.model.user.UserAuthority; +import org.teapot.backend.repository.organization.MemberRepository; +import org.teapot.backend.repository.organization.OrganizationRepository; +import org.teapot.backend.repository.user.UserRepository; +import org.teapot.backend.util.LinkBuilder; + +import java.time.LocalDate; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.security.oauth2.common.OAuth2AccessToken.BEARER_TYPE; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + + +public class OrganizationControllerIT extends AbstractControllerIT { + + private static final String ORGANIZATIONS_URL = "organizations"; + private static final String MEMBERS_URL = "members"; + private static final String USERS_URL = "users"; + + @Autowired + private OrganizationRepository organizationRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private LinkBuilder linkBuilder; + + private Organization getOrganization = new Organization(); + private User user1; + private User user2; + private Member creator; + private Member member2; + private Organization deleteOrganizationAdmin; + private Organization deleteOrganizationCreator; + private Member deleteOrganizationCreatorCreator; + private User user3; + private User user4; + private User user5; + private User user6; + private User user7; + private User user8; + private User user9; + private Member worker; + private Member owner1; + private Member owner2; + private Member owner3; + private Member worker2; + private Member worker3; + private Member postMember; + private Organization postOrganization; + private Organization repeatPostOrganization; + + private String creatorAccessToken; + private String workerAccessToken; + private String owner1AccessToken; + + @Before + public void addTestDate() throws Exception { + getOrganization.setName("getOrganization"); + getOrganization.setCreationDate(LocalDate.now()); + organizationRepository.save(getOrganization); + + user1 = new User(); + user1.setUsername("user1"); + user1.setEmail("user1@email.com"); + user1.setPassword(passwordEncoder.encode("pass")); + user1.setActivated(true); + user1.setFirstName("user1"); + user1.setLastName("user1"); + userRepository.save(user1); + + creatorAccessToken = obtainAccessToken("user1@email.com", "pass"); + + creator = new Member(); + creator.setOrganization(getOrganization); + creator.setAdmissionDate(LocalDate.now()); + creator.setStatus(MemberStatus.CREATOR); + creator.setUser(user1); + memberRepository.save(creator); + + user2 = new User(); + user2.setUsername("user2"); + user2.setEmail("user2@email.com"); + user2.setPassword(passwordEncoder.encode("pass")); + userRepository.save(user2); + + member2 = new Member(); + member2.setOrganization(getOrganization); + member2.setAdmissionDate(LocalDate.now()); + member2.setStatus(MemberStatus.WORKER); + member2.setUser(user2); + memberRepository.save(member2); + + deleteOrganizationAdmin = new Organization(); + deleteOrganizationAdmin.setName("deleteOrganizationAdmin"); + organizationRepository.save(deleteOrganizationAdmin); + + deleteOrganizationCreator = new Organization(); + deleteOrganizationCreator.setName("deleteOrganizationCreator"); + organizationRepository.save(deleteOrganizationCreator); + + deleteOrganizationCreatorCreator = new Member(); + deleteOrganizationCreatorCreator.setStatus(MemberStatus.CREATOR); + deleteOrganizationCreatorCreator.setOrganization(deleteOrganizationCreator); + deleteOrganizationCreatorCreator.setUser(user1); + memberRepository.save(member2); + deleteOrganizationCreator.getMembers().add(deleteOrganizationCreatorCreator); + + user3 = new User(); + user3.setUsername("user3"); + user3.setEmail("user3@email.com"); + user3.setPassword(passwordEncoder.encode("pass")); + user3.setActivated(true); + userRepository.save(user3); + + workerAccessToken = obtainAccessToken("user3@email.com", "pass"); + + user4 = new User(); + user4.setUsername("user4"); + user4.setEmail("user4@email.com"); + user4.setPassword(passwordEncoder.encode("pass")); + user4.setActivated(true); + userRepository.save(user4); + + user5 = new User(); + user5.setUsername("user5"); + user5.setEmail("user5@email.com"); + user5.setPassword(passwordEncoder.encode("pass")); + user5.setActivated(true); + userRepository.save(user5); + + user6 = new User(); + user6.setUsername("user6"); + user6.setEmail("user6@email.com"); + user6.setPassword(passwordEncoder.encode("pass")); + user6.setActivated(true); + userRepository.save(user6); + + user7 = new User(); + user7.setUsername("user7"); + user7.setEmail("user7@email.com"); + user7.setPassword(passwordEncoder.encode("pass")); + user7.setActivated(true); + userRepository.save(user7); + + user8 = new User(); + user8.setUsername("user8"); + user8.setEmail("user8@email.com"); + user8.setPassword(passwordEncoder.encode("pass")); + user8.setActivated(true); + userRepository.save(user8); + + user9 = new User(); + user9.setUsername("user9"); + user9.setEmail("user9@email.com"); + user9.setPassword(passwordEncoder.encode("pass")); + user9.setActivated(true); + userRepository.save(user9); + + worker = new Member(); + worker.setUser(user3); + worker.setOrganization(getOrganization); + worker.setStatus(MemberStatus.WORKER); + memberRepository.save(worker); + + owner1 = new Member(); + owner1.setUser(user4); + owner1.setOrganization(getOrganization); + owner1.setStatus(MemberStatus.OWNER); + memberRepository.save(owner1); + + owner1AccessToken = obtainAccessToken("user4@email.com", "pass"); + + owner2 = new Member(); + owner2.setUser(user5); + owner2.setOrganization(getOrganization); + owner2.setStatus(MemberStatus.OWNER); + memberRepository.save(owner2); + + owner3 = new Member(); + owner3.setUser(user6); + owner3.setOrganization(getOrganization); + owner3.setStatus(MemberStatus.OWNER); + memberRepository.save(owner3); + + worker2 = new Member(); + worker2.setUser(user7); + worker2.setOrganization(getOrganization); + worker2.setStatus(MemberStatus.WORKER); + memberRepository.save(worker2); + + worker3 = new Member(); + worker3.setUser(user8); + worker3.setOrganization(getOrganization); + worker3.setStatus(MemberStatus.WORKER); + memberRepository.save(worker3); + + postOrganization = new Organization(); + postOrganization.setName("postOrganization"); + + repeatPostOrganization = new Organization(); + repeatPostOrganization.setName("repeatPostOrganization"); + organizationRepository.save(repeatPostOrganization); + + postMember = new Member(); + postMember.setUser(user9); + postMember.setStatus(MemberStatus.WORKER); + } + + // GET ORGANIZATIONS + + @Test + public void getOrganizationsTestByUser() throws Exception { + List all = organizationRepository.findAll(); + mockMvc.perform(get(String.format("/%s", ORGANIZATIONS_URL)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, userAccessToken))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", hasSize(all.size()))) + .andExpect(jsonPath("$[0].id", is(all.get(0).getId().intValue()))) + .andExpect(jsonPath("$[0].name", is(all.get(0).getName()))) + .andExpect(jsonPath("$[0].fullName", is(all.get(0).getFullName()))) + .andExpect(jsonPath("$[0].members", + is(linkBuilder.format("/%s/%d/%s", ORGANIZATIONS_URL, + all.get(0).getId().intValue(), MEMBERS_URL)))); + } + + @Test + public void getSingleOrganizationByIdTestByUser() throws Exception { + mockMvc.perform(get(String.format("/%s/%d", ORGANIZATIONS_URL, getOrganization.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, userAccessToken))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is(getOrganization.getId().intValue()))) + .andExpect(jsonPath("$.name", is(getOrganization.getName()))) + .andExpect(jsonPath("$.fullName", is(getOrganization.getFullName()))) + .andExpect(jsonPath("$.members", + is(linkBuilder.format("/%s/%d/%s", ORGANIZATIONS_URL, + getOrganization.getId().intValue(), MEMBERS_URL)))); + } + + @Test + public void getNotExistsOrganizationTestByUser() throws Exception { + mockMvc.perform(get(String.format("/%s/-1", ORGANIZATIONS_URL)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, userAccessToken))) + .andExpect(status().isNotFound()); + } + + // GET MEMBERS + + @Test + public void getMembersInOrganizationByAdmin() throws Exception { + List members = memberRepository.findAllByOrganization(getOrganization); + + mockMvc.perform(get(String.format("/%s/%d/%s", ORGANIZATIONS_URL, getOrganization.getId(), MEMBERS_URL)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", hasSize(members.size()))) + .andExpect(jsonPath("$[0].id", is(members.get(0).getId().intValue()))) + .andExpect(jsonPath("$[0].status", is(members.get(0).getStatus().toString()))) + .andExpect(jsonPath("$[0].user", + is(linkBuilder.format("/%s/%d", + USERS_URL, members.get(0).getUser().getId())))) + .andExpect(jsonPath("$[0].organization", + is(linkBuilder.format("/%s/%d", + ORGANIZATIONS_URL, members.get(0).getOrganization().getId())))); + } + + @Test + public void getMembersInOrganizationByMember() throws Exception { + List members = memberRepository.findAllByOrganization(getOrganization); + + mockMvc.perform(get(String.format("/%s/%d/%s", ORGANIZATIONS_URL, getOrganization.getId(), MEMBERS_URL)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, creatorAccessToken))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", hasSize(members.size()))) + .andExpect(jsonPath("$[0].id", is(members.get(0).getId().intValue()))) + .andExpect(jsonPath("$[0].status", is(members.get(0).getStatus().toString()))) + .andExpect(jsonPath("$[0].user", + is(linkBuilder.format("/%s/%d", + USERS_URL, members.get(0).getUser().getId())))) + .andExpect(jsonPath("$[0].organization", + is(linkBuilder.format("/%s/%d", + ORGANIZATIONS_URL, members.get(0).getOrganization().getId())))); + } + + @Test + public void getMemberInOrganizationByAdmin() throws Exception { + Member member = memberRepository.findAllByOrganization(getOrganization).get(0); + + mockMvc.perform(get(String.format("/%s/%d/%s/%d", + ORGANIZATIONS_URL, member.getOrganization().getId(), MEMBERS_URL, member.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is(member.getId().intValue()))) + .andExpect(jsonPath("$.status", is(member.getStatus().toString()))) + .andExpect(jsonPath("$.user", + is(linkBuilder.format("/%s/%d", + USERS_URL, member.getUser().getId())))) + .andExpect(jsonPath("$.organization", + is(linkBuilder.format("/%s/%d", + ORGANIZATIONS_URL, member.getOrganization().getId())))); + } + + @Test + public void getMemberInOrganizationByMember() throws Exception { + Member member = memberRepository.findAllByOrganization(getOrganization).get(0); + + mockMvc.perform(get(String.format("/%s/%d/%s/%d", + ORGANIZATIONS_URL, member.getOrganization().getId(), MEMBERS_URL, member.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, creatorAccessToken))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", is(member.getId().intValue()))) + .andExpect(jsonPath("$.status", is(member.getStatus().toString()))) + .andExpect(jsonPath("$.user", + is(linkBuilder.format("/%s/%d", + USERS_URL, member.getUser().getId())))) + .andExpect(jsonPath("$.organization", + is(linkBuilder.format("/%s/%d", + ORGANIZATIONS_URL, member.getOrganization().getId())))); + } + + @Test + public void getNotExistsMemberInOrganizationByAdmin() throws Exception { + Member member = memberRepository.findAllByOrganization(getOrganization).get(0); + + mockMvc.perform(get(String.format("/%s/%d/%s/%d", + ORGANIZATIONS_URL, member.getOrganization().getId(), MEMBERS_URL, -1)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken))) + .andExpect(status().isNotFound()); + } + + // DELETE ORGANIZATIONS + + @Test + public void deleteOrganizationTestByAnonymous() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d", ORGANIZATIONS_URL, getOrganization.getId()))) + .andExpect(status().isUnauthorized()); + } + + @Test + public void deleteOrganizationTestByUser() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d", ORGANIZATIONS_URL, getOrganization.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, userAccessToken))) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteOrganizationTestByAdmin() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d", ORGANIZATIONS_URL, deleteOrganizationAdmin.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken))) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteNotExistsOrganizationTestByAdmin() throws Exception { + mockMvc.perform(delete(String.format("/%s/-1", ORGANIZATIONS_URL)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken))) + .andExpect(status().isNotFound()); + } + + @Test + public void deleteNotExistsOrganizationTestByCreator() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d", ORGANIZATIONS_URL, deleteOrganizationCreator.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, creatorAccessToken))) + .andExpect(status().isNoContent()); + } + + // DELETE MEMBERS + + @Test + public void deleteCreatorTestByOwner() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d/%s/%d", ORGANIZATIONS_URL, getOrganization.getId(), + MEMBERS_URL, creator.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, owner1AccessToken))) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteCreatorTestByWorker() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d/%s/%d", ORGANIZATIONS_URL, getOrganization.getId(), + MEMBERS_URL, creator.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, workerAccessToken))) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteOwnerTestByWorker() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d/%s/%d", ORGANIZATIONS_URL, getOrganization.getId(), + MEMBERS_URL, owner2.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, workerAccessToken))) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteOwnerTestByCreator() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d/%s/%d", ORGANIZATIONS_URL, getOrganization.getId(), + MEMBERS_URL, owner2.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, creatorAccessToken))) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteOwnerTestByOwner() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d/%s/%d", ORGANIZATIONS_URL, getOrganization.getId(), + MEMBERS_URL, owner3.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, owner1AccessToken))) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteWorkerTestByWorker() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d/%s/%d", ORGANIZATIONS_URL, getOrganization.getId(), + MEMBERS_URL, worker.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, workerAccessToken))) + .andExpect(status().isForbidden()); + } + + @Test + public void deleteWorkerTestByOwner() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d/%s/%d", ORGANIZATIONS_URL, getOrganization.getId(), + MEMBERS_URL, worker2.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, owner1AccessToken))) + .andExpect(status().isNoContent()); + } + + @Test + public void deleteWorkerTestByCreator() throws Exception { + mockMvc.perform(delete(String.format("/%s/%d/%s/%d", ORGANIZATIONS_URL, getOrganization.getId(), + MEMBERS_URL, worker3.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, creatorAccessToken))) + .andExpect(status().isNoContent()); + } + + // POST ORGANIZATIONS + + @Test + public void createOrganizationByAdmin() throws Exception { + mockMvc.perform(post(String.format("/%s", ORGANIZATIONS_URL)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .content(json(postOrganization)) + .contentType(contentType)) + .andExpect(status().isCreated()); + Assert.assertNotNull(organizationRepository.findByName(postOrganization.getName())); + } + + @Test + public void createExistingOrganizationByAdmin() throws Exception { + mockMvc.perform(post(String.format("/%s", ORGANIZATIONS_URL)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .content(json(repeatPostOrganization)) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + // POST MEMBERS + + @Test + public void addMemberToOrganizationByAdmin() throws Exception { + mockMvc.perform(post(String.format("/%s/%d/%s", ORGANIZATIONS_URL, getOrganization.getId(), MEMBERS_URL)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .content(String.format("{\"userId\":%d,\"status\":\"%s\"}", + postMember.getUser().getId(), postMember.getStatus())) + .contentType(contentType)) + .andExpect(status().isCreated()); + Assert.assertNotNull(memberRepository.findByOrganizationAndUser(getOrganization, postMember.getUser())); + } + + @Test + public void addExistingMemberToOrganizationByAdmin() throws Exception { + mockMvc.perform(post(String.format("/%s/%d/%s", ORGANIZATIONS_URL, getOrganization.getId(), MEMBERS_URL)) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .content(String.format("{\"userId\":%d,\"status\":\"%s\"}", + creator.getUser().getId(), creator.getStatus())) + .contentType(contentType)) + .andExpect(status().isBadRequest()); + } + + // PATCH ORGANIZATIONS + + @Test + public void changeNameTestByAdmin() throws Exception { + mockMvc.perform(patch(String.format("/%s/%d", ORGANIZATIONS_URL, getOrganization.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .param("name", "newName")) + .andExpect(status().isNoContent()); + + Organization org = organizationRepository.findOne(getOrganization.getId()); + Assert.assertEquals("newName", org.getName()); + } + + @Test + public void changeFullNameTestByAdmin() throws Exception { + mockMvc.perform(patch(String.format("/%s/%d", ORGANIZATIONS_URL, getOrganization.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .param("fullName", "newFullName")) + .andExpect(status().isNoContent()); + + Organization org = organizationRepository.findOne(getOrganization.getId()); + Assert.assertEquals("newFullName", org.getFullName()); + } + + @Test + public void changeNameAndFullNameTestByAdmin() throws Exception { + mockMvc.perform(patch(String.format("/%s/%d", ORGANIZATIONS_URL, getOrganization.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .param("name", "newName1") + .param("fullName", "newFullName1")) + .andExpect(status().isNoContent()); + + Organization org = organizationRepository.findOne(getOrganization.getId()); + Assert.assertEquals("newName1", org.getName()); + Assert.assertEquals("newFullName1", org.getFullName()); + } + + @Test + public void changeNameAndFullNameTestByCreator() throws Exception { + mockMvc.perform(patch(String.format("/%s/%d", ORGANIZATIONS_URL, getOrganization.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, creatorAccessToken)) + .param("name", "newName1") + .param("fullName", "newFullName1")) + .andExpect(status().isNoContent()); + + Organization org = organizationRepository.findOne(getOrganization.getId()); + Assert.assertEquals("newName1", org.getName()); + Assert.assertEquals("newFullName1", org.getFullName()); + } + + @Test + public void changeNameAndFullNameTestByOwner() throws Exception { + mockMvc.perform(patch(String.format("/%s/%d", ORGANIZATIONS_URL, getOrganization.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, owner1AccessToken)) + .param("name", "newName1") + .param("fullName", "newFullName1")) + .andExpect(status().isNoContent()); + + Organization org = organizationRepository.findOne(getOrganization.getId()); + Assert.assertEquals("newName1", org.getName()); + Assert.assertEquals("newFullName1", org.getFullName()); + } + + // PATCH MEMBERS + + @Test + public void changeCreatorStatusToWorkerTestByAdmin() throws Exception { + mockMvc.perform(patch(String.format("/%s/%d/%s/%d", + ORGANIZATIONS_URL, getOrganization.getId(), MEMBERS_URL, creator.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .param("status", "WORKER")) + .andExpect(status().isForbidden()); + } + + @Test + public void changeOwnerStatusToCreatorTestByAdmin() throws Exception { + mockMvc.perform(patch(String.format("/%s/%d/%s/%d", + ORGANIZATIONS_URL, getOrganization.getId(), MEMBERS_URL, owner1.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .param("status", "CREATOR")) + .andExpect(status().isForbidden()); + } + + @Test + public void changeOwnerStatusToWorkerTestByAdmin() throws Exception { + mockMvc.perform(patch(String.format("/%s/%d/%s/%d", + ORGANIZATIONS_URL, getOrganization.getId(), MEMBERS_URL, owner2.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .param("status", "WORKER")) + .andExpect(status().isNoContent()); + Assert.assertEquals(MemberStatus.WORKER, + memberRepository.findByOrganizationAndId(getOrganization, owner2.getId()).getStatus()); + } + + @Test + public void changeWorkerStatusToOwnerTestByAdmin() throws Exception { + mockMvc.perform(patch(String.format("/%s/%d/%s/%d", + ORGANIZATIONS_URL, getOrganization.getId(), MEMBERS_URL, worker2.getId())) + .header(AUTHORIZATION, String.format("%s %s", BEARER_TYPE, adminAccessToken)) + .param("status", "OWNER")) + .andExpect(status().isNoContent()); + Assert.assertEquals(MemberStatus.OWNER, + memberRepository.findByOrganizationAndId(getOrganization, worker2.getId()).getStatus()); + } +} diff --git a/src/test/java/org/teapot/backend/test/controller/TeapotPropertyControllerIT.java b/src/test/java/org/teapot/backend/test/controller/TeapotPropertyControllerIT.java index dd80a1f..50cc05f 100644 --- a/src/test/java/org/teapot/backend/test/controller/TeapotPropertyControllerIT.java +++ b/src/test/java/org/teapot/backend/test/controller/TeapotPropertyControllerIT.java @@ -3,8 +3,9 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; import org.teapot.backend.model.meta.TeapotProperty; -import org.teapot.backend.repository.TeapotPropertyRepository; +import org.teapot.backend.repository.meta.TeapotPropertyRepository; import java.util.Arrays; import java.util.List; @@ -78,7 +79,7 @@ public void addNonexistentPropertyTest() throws Exception { mockMvc.perform(post(API_URL).content(json(nonexistentProperty)) .contentType(contentType)) .andExpect(status().isCreated()) - .andExpect(header().string("Location", containsString(API_URL))); + .andExpect(header().string(HttpHeaders.LOCATION, containsString(API_URL))); } @Test @@ -86,7 +87,7 @@ public void addExistentPropertyTest() throws Exception { mockMvc.perform(post(API_URL).content(json(existentProperty)) .contentType(contentType)) .andExpect(status().isBadRequest()) - .andExpect(header().doesNotExist("Location")); + .andExpect(header().doesNotExist(HttpHeaders.LOCATION)); } @Test diff --git a/src/test/java/org/teapot/backend/test/controller/UserControllerIT.java b/src/test/java/org/teapot/backend/test/controller/UserControllerIT.java index 06fffd6..2b43593 100644 --- a/src/test/java/org/teapot/backend/test/controller/UserControllerIT.java +++ b/src/test/java/org/teapot/backend/test/controller/UserControllerIT.java @@ -3,10 +3,11 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.springframework.http.HttpHeaders; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.teapot.backend.model.User; -import org.teapot.backend.model.UserAuthority; +import org.teapot.backend.model.user.User; +import org.teapot.backend.model.user.UserAuthority; import java.time.LocalDate; import java.util.List; @@ -84,11 +85,9 @@ public void getUsersTest() throws Exception { .andExpect(jsonPath("$[0].id", is(all.get(0).getId().intValue()))) .andExpect(jsonPath("$[0].username", is(all.get(0).getUsername()))) .andExpect(jsonPath("$[0].email", is(all.get(0).getEmail()))) - .andExpect(jsonPath("$[0].password", is(all.get(0).getPassword()))) .andExpect(jsonPath("$[1].id", is(all.get(1).getId().intValue()))) .andExpect(jsonPath("$[1].username", is(all.get(1).getUsername()))) - .andExpect(jsonPath("$[1].email", is(all.get(1).getEmail()))) - .andExpect(jsonPath("$[1].password", is(all.get(1).getPassword()))); + .andExpect(jsonPath("$[1].email", is(all.get(1).getEmail()))); } @Test @@ -98,8 +97,7 @@ public void getSingleUserByIdTest() throws Exception { .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.id", is(getUserOne.getId().intValue()))) .andExpect(jsonPath("$.username", is(getUserOne.getUsername()))) - .andExpect(jsonPath("$.email", is(getUserOne.getEmail()))) - .andExpect(jsonPath("$.password", is(getUserOne.getPassword()))); + .andExpect(jsonPath("$.email", is(getUserOne.getEmail()))); } @Test @@ -116,7 +114,7 @@ public void registerUserTestByAnonymous() throws Exception { .content(json(postUserOne)) .contentType(contentType)) .andExpect(status().isCreated()) - .andExpect(header().string("Location", containsString(API_URL))); + .andExpect(header().string(HttpHeaders.LOCATION, containsString(API_URL))); } @Test @@ -135,7 +133,7 @@ public void registerUserTestByAdmin() throws Exception { .content(json(postUserTwo)) .contentType(contentType)) .andExpect(status().isCreated()) - .andExpect(header().string("Location", containsString(API_URL))); + .andExpect(header().string(HttpHeaders.LOCATION, containsString(API_URL))); } @Test @@ -145,7 +143,7 @@ public void repeatRegisterUserTestByAdmin() throws Exception { .content(json(repeatedPostUser)) .contentType(contentType)) .andExpect(status().isBadRequest()) - .andExpect(header().doesNotExist("Location")); + .andExpect(header().doesNotExist(HttpHeaders.LOCATION)); } // PUT diff --git a/src/test/java/org/teapot/backend/test/repository/MemberRepositoryIT.java b/src/test/java/org/teapot/backend/test/repository/MemberRepositoryIT.java new file mode 100644 index 0000000..e71e0ad --- /dev/null +++ b/src/test/java/org/teapot/backend/test/repository/MemberRepositoryIT.java @@ -0,0 +1,111 @@ +package org.teapot.backend.test.repository; + +import org.assertj.core.util.Lists; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.teapot.backend.model.organization.Member; +import org.teapot.backend.model.organization.MemberStatus; +import org.teapot.backend.model.organization.Organization; +import org.teapot.backend.model.user.User; +import org.teapot.backend.repository.organization.MemberRepository; +import org.teapot.backend.repository.organization.OrganizationRepository; +import org.teapot.backend.repository.user.UserRepository; +import org.teapot.backend.test.AbstractIT; + +import java.time.LocalDate; +import java.util.Arrays; + + +public class MemberRepositoryIT extends AbstractIT { + + @Autowired + private OrganizationRepository organizationRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private UserRepository userRepository; + + private Organization memberRepositoryTestOrg = new Organization(); + private Organization findByUserTestOrg = new Organization(); + private User user1; + private User user2; + private Member member1; + private Member member2; + private Member member3; + + @Before + public void setup() { + memberRepository.deleteAllInBatch(); + userRepository.deleteAllInBatch(); + organizationRepository.deleteAllInBatch(); + + memberRepositoryTestOrg.setName("memberRepositoryTestOrg"); + memberRepositoryTestOrg.setCreationDate(LocalDate.now()); + organizationRepository.save(memberRepositoryTestOrg); + + findByUserTestOrg.setName("findByUserTestOrg"); + organizationRepository.save(findByUserTestOrg); + + user1 = new User(); + user1.setUsername("u1"); + user1.setEmail("u1@email.com"); + user1.setPassword("pass"); + userRepository.save(user1); + + member1 = new Member(); + member1.setOrganization(memberRepositoryTestOrg); + member1.setAdmissionDate(LocalDate.now()); + member1.setStatus(MemberStatus.CREATOR); + member1.setUser(user1); + memberRepository.save(member1); + + user2 = new User(); + user2.setUsername("u2"); + user2.setEmail("u2@email.com"); + user2.setPassword("pass"); + userRepository.save(user2); + + member2 = new Member(); + member2.setOrganization(memberRepositoryTestOrg); + member2.setAdmissionDate(LocalDate.now()); + member2.setStatus(MemberStatus.WORKER); + member2.setUser(user2); + memberRepository.save(member2); + + member3 = new Member(); + member3.setOrganization(findByUserTestOrg); + member3.setAdmissionDate(LocalDate.now()); + member3.setStatus(MemberStatus.CREATOR); + member3.setUser(user1); + memberRepository.save(member3); + } + + @Test + public void findAllByOrganizationTest() { + Assert.assertEquals(Arrays.asList(member1, member2), + memberRepository.findAllByOrganization(memberRepositoryTestOrg)); + } + + @Test + public void findByOrganizationAndIdTest() { + Assert.assertEquals(member1, memberRepository + .findByOrganizationAndId(memberRepositoryTestOrg, member1.getId())); + } + + @Test + public void findByOrganizationAndUserTest() { + Assert.assertEquals(member2, memberRepository + .findByOrganizationAndUser(memberRepositoryTestOrg, user2)); + } + + @Test + public void findByUserTest() { + Assert.assertEquals(Lists.newArrayList(member1, member3) + ,memberRepository.findByUser(user1) + ); + } +} diff --git a/src/test/java/org/teapot/backend/test/repository/OrganizationRepositoryIT.java b/src/test/java/org/teapot/backend/test/repository/OrganizationRepositoryIT.java new file mode 100644 index 0000000..a5aa074 --- /dev/null +++ b/src/test/java/org/teapot/backend/test/repository/OrganizationRepositoryIT.java @@ -0,0 +1,35 @@ +package org.teapot.backend.test.repository; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.teapot.backend.model.organization.Organization; +import org.teapot.backend.repository.organization.OrganizationRepository; +import org.teapot.backend.test.AbstractIT; + +import java.time.LocalDate; + + +public class OrganizationRepositoryIT extends AbstractIT { + + @Autowired + private OrganizationRepository organizationRepository; + + private Organization findByNameTestOrganization = new Organization(); + + @Before + public void setup() { + findByNameTestOrganization.setName("findByNameTestOrganization"); + findByNameTestOrganization.setCreationDate(LocalDate.now()); + organizationRepository.save(findByNameTestOrganization); + } + + @Test + public void findByNameTest() { + Organization fromDataBase = organizationRepository + .findByName("findByNameTestOrganization"); + Assert.assertNotNull(fromDataBase); + Assert.assertEquals(findByNameTestOrganization, fromDataBase); + } +} diff --git a/src/test/java/org/teapot/backend/test/repository/TeapotPropertyRepositoryIT.java b/src/test/java/org/teapot/backend/test/repository/TeapotPropertyRepositoryIT.java index ca0d3b1..fc34fa2 100644 --- a/src/test/java/org/teapot/backend/test/repository/TeapotPropertyRepositoryIT.java +++ b/src/test/java/org/teapot/backend/test/repository/TeapotPropertyRepositoryIT.java @@ -5,7 +5,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.teapot.backend.model.meta.TeapotProperty; -import org.teapot.backend.repository.TeapotPropertyRepository; +import org.teapot.backend.repository.meta.TeapotPropertyRepository; import org.teapot.backend.test.AbstractIT; public class TeapotPropertyRepositoryIT extends AbstractIT { diff --git a/src/test/java/org/teapot/backend/test/repository/UserRepositoryIT.java b/src/test/java/org/teapot/backend/test/repository/UserRepositoryIT.java index a74e460..e0b2146 100644 --- a/src/test/java/org/teapot/backend/test/repository/UserRepositoryIT.java +++ b/src/test/java/org/teapot/backend/test/repository/UserRepositoryIT.java @@ -5,9 +5,9 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; -import org.teapot.backend.model.User; -import org.teapot.backend.model.UserAuthority; -import org.teapot.backend.repository.UserRepository; +import org.teapot.backend.model.user.User; +import org.teapot.backend.model.user.UserAuthority; +import org.teapot.backend.repository.user.UserRepository; import org.teapot.backend.test.AbstractIT;