diff --git a/.docker/application.properties.tpl b/.docker/application.properties.tpl index 83cbcdc..a272ea4 100644 --- a/.docker/application.properties.tpl +++ b/.docker/application.properties.tpl @@ -15,7 +15,9 @@ spring.jpa.properties.hibernate.show_sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.use_sql_comments=true spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true -spring.liquibase.enabled={{default .Env.PATTERN_ATLAS_FETCH_INITIAL_DATA "false"}} +spring.liquibase.enabled=true +spring.liquibase.change-log=file:patternatlas.xml spring.liquibase.password={{.Env.DB_INIT_PASSWORD}} spring.liquibase.user={{.Env.DB_INIT_USER}} -spring.liquibase.url=jdbc:postgresql://{{.Env.JDBC_DATABASE_URL}}:{{.Env.JDBC_DATABASE_PORT}}/{{.Env.JDBC_DATABASE_NAME}} \ No newline at end of file +spring.liquibase.url=jdbc:postgresql://{{.Env.JDBC_DATABASE_URL}}:{{.Env.JDBC_DATABASE_PORT}}/{{.Env.JDBC_DATABASE_NAME}} +security.oauth2.resource.jwk.key-set-uri={{.Env.JWK_URI}} \ No newline at end of file diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml new file mode 100644 index 0000000..053a679 --- /dev/null +++ b/.docker/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3' +services: + db: + image: postgres:10 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - "5432:5432" + networks: + - default +networks: + default: + driver: bridge diff --git a/.gitignore b/.gitignore index 173b6aa..2da8a1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ + HELP.md target/ !.mvn/wrapper/maven-wrapper.jar @@ -30,10 +31,13 @@ build/ ### VS Code ### .vscode/ +### MacOS File System ### +**/.DS_STORE + ### TexRendering ### *.png *.tex *.pdf *.log *.aux -*.svg \ No newline at end of file +*.svg diff --git a/Dockerfile b/Dockerfile index 6cfca2a..2ae75e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,6 +36,8 @@ ENV JDBC_DATABASE_NAME postgres ENV JDBC_DATABASE_PORT 5060 ENV HAL_EXPLORER true +ENV JWK_URI "http://localhost:8080/realms/patternatlas/protocol/openid-connect/certs" + RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz diff --git a/docs/dev/index.md b/docs/dev/index.md index 9fe370a..ec62640 100644 --- a/docs/dev/index.md +++ b/docs/dev/index.md @@ -1,10 +1,14 @@ # PatternAtlas Developer Guide -This document provides an index to all development guidelines and background information of the PatternPedia. +This document provides an index to all development guidelines and background information of the PatternAtlas. - [ADR](/adr) - Information on Architectural decisions can be found here ## Quick Develop - -1. Clone the repository `git clone https://github.com/PatternPedia/pattern-atlas-api.git`. +1. Clone the repository `git clone https://github.com/PatternAtlas/pattern-atlas-api.git`. 2. Build the repository `mvn package -DskipTests` (skiping the tests for a faster build), Java 8 required. -3. Clone the repository `git clone https://github.com/PatternPedia/pattern-atlas-ui.git`. +3. Clone the repository `git clone https://github.com/PatternAtlas/pattern-atlas-ui.git`. 4. Build the repository `mvn package -DskipTests` (skiping the tests for a faster build), npm is required. (plus yarn, optionally) 5. Continue your IDE setup: - [IntelliJ Ultimate](IntelliJ/) diff --git a/pom.xml b/pom.xml index 0c4906d..a60d28e 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ 1.8 1.5.9 2.5.0 + 2.13.2 @@ -137,11 +138,6 @@ - - org.springdoc - springdoc-openapi-ui - ${springdoc-ui.version} - org.springdoc springdoc-openapi-data-rest @@ -169,7 +165,17 @@ com.fasterxml.jackson.core jackson-databind - 2.10.0 + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} diff --git a/src/main/java/io/github/patternatlas/api/PatternAtlasAPI.java b/src/main/java/io/github/patternatlas/api/PatternAtlasAPI.java index 1d20522..84f3222 100644 --- a/src/main/java/io/github/patternatlas/api/PatternAtlasAPI.java +++ b/src/main/java/io/github/patternatlas/api/PatternAtlasAPI.java @@ -4,20 +4,33 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; - -import com.vladmihalcea.hibernate.type.util.Configuration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Contact; import io.swagger.v3.oas.annotations.info.Info; + +import com.vladmihalcea.hibernate.type.util.Configuration; + import lombok.extern.slf4j.Slf4j; +import io.github.patternatlas.api.rest.controller.UserController; +import io.github.patternatlas.api.service.IssueService; + @EnableTransactionManagement @Slf4j +@RestController @SpringBootApplication @OpenAPIDefinition(info = @Info(title = "pattern-atlas-api", version = "1.0", contact = @Contact(url = "https://github.com/PatternAtlas/pattern-atlas-api", name = "Pattern Atlas API"))) public class PatternAtlasAPI implements CommandLineRunner { + @Autowired + private UserController userController; + + @Autowired + private IssueService issueService; + public static void main(String[] args) { System.setProperty(Configuration.PropertyKey.PRINT_BANNER.getKey(), Boolean.FALSE.toString()); SpringApplication.run(PatternAtlasAPI.class, args); diff --git a/src/main/java/io/github/patternatlas/api/config/Authority.java b/src/main/java/io/github/patternatlas/api/config/Authority.java new file mode 100644 index 0000000..4c5c109 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/config/Authority.java @@ -0,0 +1,29 @@ +package io.github.patternatlas.api.config; + +public interface Authority { + + // TODO will be phased out - after all authority checks are changed to hasResourcePermission, this file should not be + // needed anymore + + /** Pattern */ + String APPROVED_PATTERN_READ = "hasAuthority('APPROVED_PATTERN_READ')"; + String APPROVED_PATTERN_CREATE = "hasAuthority('APPROVED_PATTERN_CREATE')"; + String APPROVED_PATTERN_EDIT = "hasAuthority('APPROVED_PATTERN_EDIT')"; + String APPROVED_PATTERN_DELETE = "hasAuthority('APPROVED_PATTERN_DELETE')"; + String APPROVED_PATTERN_READ_ALL = "hasAuthority('APPROVED_PATTERN_READ_ALL')"; + String APPROVED_PATTERN_EDIT_ALL = "hasAuthority('APPROVED_PATTERN_EDIT_ALL')"; + String APPROVED_PATTERN_DELETE_ALL = "hasAuthority('APPROVED_PATTERN_DELETE_ALL')"; + /** USER */ + String USER_READ = "hasAuthority('USER_READ')"; + String USER_CREATE = "hasAuthority('USER_CREATE')"; + String USER_EDIT = "hasAuthority('USER_EDIT')"; + String USER_DELETE = "hasAuthority('USER_DELETE')"; + String USER_READ_ALL = "hasAuthority('USER_READ_ALL')"; + String USER_EDIT_ALL = "hasAuthority('USER_EDIT_ALL')"; + String USER_DELETE_ALL = "hasAuthority('USER_DELETE_ALL')"; + String USER_ALL = "hasAuthority('USER_ALL')"; + /** GENERAL */ + String COMMENT = "hasAuthority('COMMENT')"; + String VOTE = "hasAuthority('VOTE')"; + String EVIDENCE = "hasAuthority('EVIDENCE')"; +} diff --git a/src/main/java/io/github/patternatlas/api/config/ResourceSecurityConfig.java b/src/main/java/io/github/patternatlas/api/config/ResourceSecurityConfig.java new file mode 100644 index 0000000..6eaf368 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/config/ResourceSecurityConfig.java @@ -0,0 +1,24 @@ +package io.github.patternatlas.api.config; + +import io.github.patternatlas.api.security.ResourceMethodSecurityExpressionHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class ResourceSecurityConfig extends GlobalMethodSecurityConfiguration { + + @Autowired + private ApplicationContext applicationContext; + + @Override + protected MethodSecurityExpressionHandler createExpressionHandler() { + ResourceMethodSecurityExpressionHandler handler = new ResourceMethodSecurityExpressionHandler(); + handler.setApplicationContext(applicationContext); + return handler; + } +} diff --git a/src/main/java/io/github/patternatlas/api/config/ResourceServerConfig.java b/src/main/java/io/github/patternatlas/api/config/ResourceServerConfig.java index 027fc28..8b5da67 100644 --- a/src/main/java/io/github/patternatlas/api/config/ResourceServerConfig.java +++ b/src/main/java/io/github/patternatlas/api/config/ResourceServerConfig.java @@ -1,9 +1,11 @@ package io.github.patternatlas.api.config; +import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtAccessTokenConverterConfigurer; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -11,15 +13,19 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; +import java.util.Map; + @Configuration @EnableResourceServer -// START::Comment for local development with authorization -//@EnableGlobalMethodSecurity(prePostEnabled = true) -// END::Comment for local development with authorization class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override @@ -29,17 +35,7 @@ public void configure(HttpSecurity http) throws Exception { .antMatchers("/**") .and() .authorizeRequests() - .antMatchers("/swagger-ui/**").permitAll() - // START::Comment for local development with authorization -// .antMatchers(HttpMethod.GET, "/**").permitAll() -// .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')") -// .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')") -// .antMatchers(HttpMethod.DELETE, "/**").hasAuthority("ADMIN") -// .anyRequest().authenticated() - //END::Comment for local development with authorization - // START::Uncomment for local development without authorization .anyRequest().permitAll() - // END::Uncomment for local development without authorization .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); @@ -69,4 +65,5 @@ public FilterRegistrationBean customCorsFilter() { bean.setOrder(Ordered.HIGHEST_PRECEDENCE); return bean; } + } diff --git a/src/main/java/io/github/patternatlas/api/entities/Pattern.java b/src/main/java/io/github/patternatlas/api/entities/Pattern.java index 26f7072..9cad317 100644 --- a/src/main/java/io/github/patternatlas/api/entities/Pattern.java +++ b/src/main/java/io/github/patternatlas/api/entities/Pattern.java @@ -34,6 +34,7 @@ public class Pattern extends EntityWithURI { private PatternLanguage patternLanguage; @JsonIgnore + @ToString.Exclude @OneToMany(mappedBy = "pattern", cascade = CascadeType.ALL, orphanRemoval = true) private List patternViews = new ArrayList<>(); diff --git a/src/main/java/io/github/patternatlas/api/entities/PatternGraph.java b/src/main/java/io/github/patternatlas/api/entities/PatternGraph.java index ed26a36..7ccab25 100644 --- a/src/main/java/io/github/patternatlas/api/entities/PatternGraph.java +++ b/src/main/java/io/github/patternatlas/api/entities/PatternGraph.java @@ -11,6 +11,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Objects; + @Entity @Data @EqualsAndHashCode(callSuper = true) @@ -21,4 +23,9 @@ public abstract class PatternGraph extends EntityWithURI { @Type(type = "jsonb") @Column(columnDefinition = "jsonb") private Object graph; + + @Override + public int hashCode() { + return Objects.hash(graph); + } } diff --git a/src/main/java/io/github/patternatlas/api/entities/PatternLanguage.java b/src/main/java/io/github/patternatlas/api/entities/PatternLanguage.java index 0ca1443..57f19ce 100644 --- a/src/main/java/io/github/patternatlas/api/entities/PatternLanguage.java +++ b/src/main/java/io/github/patternatlas/api/entities/PatternLanguage.java @@ -45,4 +45,6 @@ public class PatternLanguage extends PatternGraph { @JsonIgnore @OneToMany(mappedBy = "patternLanguage", fetch = FetchType.LAZY) private List undirectedEdges; + + } diff --git a/src/main/java/io/github/patternatlas/api/entities/PatternSchema.java b/src/main/java/io/github/patternatlas/api/entities/PatternSchema.java index 5935321..da4f38a 100644 --- a/src/main/java/io/github/patternatlas/api/entities/PatternSchema.java +++ b/src/main/java/io/github/patternatlas/api/entities/PatternSchema.java @@ -1,6 +1,7 @@ package io.github.patternatlas.api.entities; import java.util.List; +import java.util.Objects; import java.util.UUID; import javax.persistence.CascadeType; import javax.persistence.Entity; @@ -33,4 +34,9 @@ public class PatternSchema { @OneToOne @MapsId private PatternLanguage patternLanguage; + + @Override + public int hashCode() { + return Objects.hash(id, patternSectionSchemas); + } } diff --git a/src/main/java/io/github/patternatlas/api/entities/PatternView.java b/src/main/java/io/github/patternatlas/api/entities/PatternView.java index 4b1bbbf..85c8cf6 100644 --- a/src/main/java/io/github/patternatlas/api/entities/PatternView.java +++ b/src/main/java/io/github/patternatlas/api/entities/PatternView.java @@ -13,6 +13,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; @Entity @Data @@ -23,14 +24,17 @@ public class PatternView extends PatternGraph { private URL logo; @JsonIgnore + @ToString.Exclude @OneToMany(mappedBy = "patternView", cascade = CascadeType.ALL, orphanRemoval = true) private List patterns = new ArrayList<>(); @JsonIgnore + @ToString.Exclude @OneToMany(mappedBy = "patternView", cascade = CascadeType.ALL, orphanRemoval = true) private List directedEdges; @JsonIgnore + @ToString.Exclude @OneToMany(mappedBy = "patternView", cascade = CascadeType.ALL, orphanRemoval = true) private List undirectedEdges; diff --git a/src/main/java/io/github/patternatlas/api/entities/candidate/Candidate.java b/src/main/java/io/github/patternatlas/api/entities/candidate/Candidate.java index 88ac5c2..aca7c48 100644 --- a/src/main/java/io/github/patternatlas/api/entities/candidate/Candidate.java +++ b/src/main/java/io/github/patternatlas/api/entities/candidate/Candidate.java @@ -3,25 +3,28 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.ListIterator; import java.util.Set; + +import io.github.patternatlas.api.entities.EntityWithURI; +import io.github.patternatlas.api.entities.PatternLanguage; +import io.github.patternatlas.api.entities.candidate.author.CandidateAuthor; +import io.github.patternatlas.api.entities.candidate.comment.CandidateComment; +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidence; +import io.github.patternatlas.api.rest.model.candidate.CandidateModelRequest; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -import io.github.patternatlas.api.entities.candidate.rating.CandidateRating; -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.rest.model.CandidateModel; -import io.github.patternatlas.api.entities.EntityWithURI; -import io.github.patternatlas.api.entities.PatternLanguage; +import javax.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; +import org.hibernate.annotations.Type; @Entity @Data @@ -36,15 +39,19 @@ public class Candidate extends EntityWithURI { @ManyToOne private PatternLanguage patternLanguage; - // @Type(type = "jsonb") -// @Column(columnDefinition = "jsonb") -// @NotNull - private String content; - - private int rating = 0; + @Type(type = "jsonb") + @Column(columnDefinition = "jsonb") + @NotNull + private Object content; private String version = "0.1.0"; + private int rating; + + @JsonIgnore + @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true) + private List authors = new ArrayList<>(); + @JsonIgnore @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true) private Set userRating = new HashSet<>(); @@ -53,40 +60,31 @@ public class Candidate extends EntityWithURI { @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true) private List comments = new ArrayList<>(); - public Candidate(CandidateModel candidateModel) { - this.setId(candidateModel.getId()); - this.setUri(candidateModel.getUri()); - this.setName(candidateModel.getName()); - this.setIconUrl(candidateModel.getIconUrl()); - //patternLanguage - this.setContent(candidateModel.getContent()); - this.setVersion(candidateModel.getVersion()); - } - - public void addComment(CandidateComment comment, UserEntity user) { - comments.add(comment); - comment.setCandidate(this); - comment.setUser(user); - } - - public void updateComment(CandidateComment updateComment) { - ListIterator commentIterator = comments.listIterator(); - while (commentIterator.hasNext()) { - CandidateComment next = commentIterator.next(); - if (next.getId().equals(updateComment.getId())) { - commentIterator.set(updateComment); - break; - } - } + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true) + private List evidences = new ArrayList<>(); + + public Candidate (CandidateModelRequest candidateModelRequest) { +// this.setId(candidateModel.getId()); + this.setUri(candidateModelRequest.getUri()); + this.setName(candidateModelRequest.getName()); + this.setIconUrl(candidateModelRequest.getIconUrl()); + this.setContent(candidateModelRequest.getContent()); + this.setVersion(candidateModelRequest.getVersion()); + this.setRating(candidateModelRequest.getRating()); } - public void removeComment(CandidateComment comment) { - comments.remove(comment); - comment.setCandidate(null); - comment.setUser(null); + public void updateCandidate(CandidateModelRequest candidateModelRequest) { + this.setId(candidateModelRequest.getId()); + this.setUri(candidateModelRequest.getUri()); + this.setName(candidateModelRequest.getName()); + this.setIconUrl(candidateModelRequest.getIconUrl()); + this.setContent(candidateModelRequest.getContent()); + this.setVersion(candidateModelRequest.getVersion()); + this.setRating(candidateModelRequest.getRating()); } public String toString() { return this.getId().toString(); } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateRating.java b/src/main/java/io/github/patternatlas/api/entities/candidate/CandidateRating.java similarity index 59% rename from src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateRating.java rename to src/main/java/io/github/patternatlas/api/entities/candidate/CandidateRating.java index 175801f..09199f3 100644 --- a/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateRating.java +++ b/src/main/java/io/github/patternatlas/api/entities/candidate/CandidateRating.java @@ -1,4 +1,4 @@ -package io.github.patternatlas.api.entities.candidate.rating; +package io.github.patternatlas.api.entities.candidate; import java.util.Objects; import javax.persistence.EmbeddedId; @@ -8,10 +8,13 @@ import io.github.patternatlas.api.entities.user.UserEntity; import io.github.patternatlas.api.entities.candidate.Candidate; +import io.github.patternatlas.api.entities.shared.CompositeKey; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; + @Entity @Data @NoArgsConstructor @@ -19,10 +22,10 @@ public class CandidateRating { @EmbeddedId @EqualsAndHashCode.Include - private CandidateRatingKey id; + private CompositeKey id; @ManyToOne - @MapsId("candidateId") + @MapsId("entityId") @EqualsAndHashCode.Include private Candidate candidate; @@ -33,15 +36,26 @@ public class CandidateRating { private int rating; + private int readability; + private int understandability; + private int appropriateness; + public CandidateRating(Candidate candidate, UserEntity user) { this.candidate = candidate; this.user = user; - this.id = new CandidateRatingKey(candidate.getId(), user.getId()); + this.id = new CompositeKey(candidate.getId(), user.getId()); + } + + public CandidateRating(Candidate candidate, UserEntity user, int readability, int understandability, int appropriateness) { + this(candidate, user); + this.readability = readability; + this.understandability = understandability; + this.appropriateness = appropriateness; } @Override public String toString() { - return this.id.toString() + this.rating; + return this.id.toString() + this.readability; } @Override @@ -51,11 +65,13 @@ public boolean equals(Object o) { CandidateRating that = (CandidateRating) o; return Objects.equals(candidate.getName(), that.candidate.getName()) && Objects.equals(user.getName(), that.user.getName()) && - Objects.equals(rating, that.rating); + Objects.equals(readability, that.readability) && + Objects.equals(understandability, that.understandability) && + Objects.equals(appropriateness, that.appropriateness); } @Override public int hashCode() { - return Objects.hash(candidate.getName(), user.getName(), rating); + return Objects.hash(candidate.getName(), user.getName(), readability); } } diff --git a/src/main/java/io/github/patternatlas/api/entities/candidate/author/CandidateAuthor.java b/src/main/java/io/github/patternatlas/api/entities/candidate/author/CandidateAuthor.java new file mode 100644 index 0000000..cacb72f --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/candidate/author/CandidateAuthor.java @@ -0,0 +1,48 @@ +package io.github.patternatlas.api.entities.candidate.author; + +import io.github.patternatlas.api.entities.candidate.Candidate; +import io.github.patternatlas.api.entities.shared.CompositeKey; +import io.github.patternatlas.api.entities.user.UserEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; + +@Entity +@Data +@NoArgsConstructor +public class CandidateAuthor { + + @EmbeddedId + @EqualsAndHashCode.Include + private CompositeKey id; + + @ManyToOne + @MapsId("entityId") + @EqualsAndHashCode.Include + private Candidate candidate; + + @ManyToOne + @MapsId("userId") + @EqualsAndHashCode.Include + private UserEntity user; + + private String role; + + public CandidateAuthor(Candidate candidate, UserEntity user) { + this.candidate = candidate; + this.user = user; + this.id = new CompositeKey(candidate.getId(), user.getId()); + } + + public CandidateAuthor(Candidate candidate, UserEntity user, String role) { + this(candidate, user); + this.role = role; + + } +} diff --git a/src/main/java/io/github/patternatlas/api/entities/candidate/CandidateComment.java b/src/main/java/io/github/patternatlas/api/entities/candidate/comment/CandidateComment.java similarity index 67% rename from src/main/java/io/github/patternatlas/api/entities/candidate/CandidateComment.java rename to src/main/java/io/github/patternatlas/api/entities/candidate/comment/CandidateComment.java index 91ed163..650b341 100644 --- a/src/main/java/io/github/patternatlas/api/entities/candidate/CandidateComment.java +++ b/src/main/java/io/github/patternatlas/api/entities/candidate/comment/CandidateComment.java @@ -1,4 +1,9 @@ -package io.github.patternatlas.api.entities.candidate; +package io.github.patternatlas.api.entities.candidate.comment; + +import io.github.patternatlas.api.entities.candidate.Candidate; +import io.github.patternatlas.api.entities.candidate.comment.CandidateCommentRating; +import io.github.patternatlas.api.entities.shared.Comment; +import io.github.patternatlas.api.entities.user.UserEntity; import java.io.Serializable; import java.util.HashSet; @@ -11,9 +16,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.candidate.rating.CandidateCommentRating; -import io.github.patternatlas.api.entities.shared.Comment; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -30,16 +32,18 @@ public class CandidateComment extends Comment implements Serializable { @ManyToOne private Candidate candidate; - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - @ToString.Exclude - @ManyToOne - private UserEntity user; - @JsonIgnore @OneToMany(mappedBy = "candidateComment", cascade = CascadeType.ALL, orphanRemoval = true) private Set userRating = new HashSet<>(); - public CandidateComment(String text) { - super(text); + private int rating = 0; + + public CandidateComment(String text, Candidate candidate, UserEntity user) { + super(text, user); + this.candidate = candidate; + } + + public void updateComment(String text) { + this.setText(text); } } diff --git a/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateCommentRating.java b/src/main/java/io/github/patternatlas/api/entities/candidate/comment/CandidateCommentRating.java similarity index 74% rename from src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateCommentRating.java rename to src/main/java/io/github/patternatlas/api/entities/candidate/comment/CandidateCommentRating.java index ef19154..7f7ec77 100644 --- a/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateCommentRating.java +++ b/src/main/java/io/github/patternatlas/api/entities/candidate/comment/CandidateCommentRating.java @@ -1,4 +1,4 @@ -package io.github.patternatlas.api.entities.candidate.rating; +package io.github.patternatlas.api.entities.candidate.comment; import java.util.Objects; import javax.persistence.EmbeddedId; @@ -6,8 +6,10 @@ import javax.persistence.ManyToOne; import javax.persistence.MapsId; -import io.github.patternatlas.api.entities.candidate.CandidateComment; +import io.github.patternatlas.api.entities.candidate.comment.CandidateComment; import io.github.patternatlas.api.entities.user.UserEntity; +import io.github.patternatlas.api.entities.shared.CompositeKey; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -19,10 +21,10 @@ public class CandidateCommentRating { @EmbeddedId @EqualsAndHashCode.Include - private CandidateCommentRatingKey id; + private CompositeKey id; @ManyToOne - @MapsId("candidateCommentId") + @MapsId("entityId") @EqualsAndHashCode.Include private CandidateComment candidateComment; @@ -36,7 +38,12 @@ public class CandidateCommentRating { public CandidateCommentRating(CandidateComment candidateComment, UserEntity user) { this.candidateComment = candidateComment; this.user = user; - this.id = new CandidateCommentRatingKey(candidateComment.getId(), user.getId()); + this.id = new CompositeKey(candidateComment.getId(), user.getId()); + } + + public CandidateCommentRating(CandidateComment candidateComment, UserEntity user, int rating) { + this(candidateComment, user); + this.rating = rating; } @Override diff --git a/src/main/java/io/github/patternatlas/api/entities/candidate/evidence/CandidateEvidence.java b/src/main/java/io/github/patternatlas/api/entities/candidate/evidence/CandidateEvidence.java new file mode 100644 index 0000000..11a38c5 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/candidate/evidence/CandidateEvidence.java @@ -0,0 +1,61 @@ +package io.github.patternatlas.api.entities.candidate.evidence; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.github.patternatlas.api.entities.candidate.Candidate; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidence; +import io.github.patternatlas.api.entities.shared.Evidence; +import io.github.patternatlas.api.entities.user.UserEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class CandidateEvidence extends Evidence implements Serializable { + + @JsonIgnore + @ToString.Exclude + @ManyToOne + private Candidate candidate; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @ToString.Exclude + @ManyToOne + private UserEntity user; + + @JsonIgnore + @OneToMany(mappedBy = "candidateEvidence", cascade = CascadeType.ALL, orphanRemoval = true) + private List userRating = new ArrayList<>(); + + public CandidateEvidence(String title, String context, String type, Boolean supporting, String source, Candidate candidate, UserEntity user) { + super(title, context, type, supporting, source); + this.candidate = candidate; + this.user = user; + } + + public CandidateEvidence(IssueEvidence issueEvidence, Candidate candidate, UserEntity user) { + this(issueEvidence.getTitle(), issueEvidence.getContext(), issueEvidence.getType(), issueEvidence.getSupporting(), issueEvidence.getSource(), candidate, user); + } + + public void updateEvidence(String title, String context, String type, Boolean supporting, String source) { + this.setTitle(title); + this.setContext(context); + this.setType(type); + this.setSupporting(supporting); + this.setSource(source); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/entities/candidate/evidence/CandidateEvidenceRating.java b/src/main/java/io/github/patternatlas/api/entities/candidate/evidence/CandidateEvidenceRating.java new file mode 100644 index 0000000..7da3a26 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/candidate/evidence/CandidateEvidenceRating.java @@ -0,0 +1,68 @@ +package io.github.patternatlas.api.entities.candidate.evidence; + +import io.github.patternatlas.api.entities.shared.CompositeKey; +import io.github.patternatlas.api.entities.user.UserEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import java.util.Objects; + +@Entity +@Data +@NoArgsConstructor +public class CandidateEvidenceRating { + + @EmbeddedId + @EqualsAndHashCode.Include + private CompositeKey id; + + @ManyToOne + @MapsId("entityId") + @EqualsAndHashCode.Include + private CandidateEvidence candidateEvidence; + + @ManyToOne + @MapsId("userId") + @EqualsAndHashCode.Include + private UserEntity user; + + private int rating; + + public CandidateEvidenceRating(CandidateEvidence candidateEvidence, UserEntity user) { + this.candidateEvidence = candidateEvidence; + this.user = user; + this.id = new CompositeKey(candidateEvidence.getId(), user.getId()); + } + + public CandidateEvidenceRating(CandidateEvidence candidateEvidence, UserEntity user, int rating) { + this(candidateEvidence, user); + this.rating = rating; + } + + @Override + public String toString() { + return this.id.toString() + this.rating; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CandidateEvidenceRating)) return false; + CandidateEvidenceRating that = (CandidateEvidenceRating) o; + return Objects.equals(candidateEvidence.getTitle(), that.candidateEvidence.getTitle()) && + Objects.equals(user.getName(), that.user.getName()) && + Objects.equals(rating, that.candidateEvidence); + } + + @Override + public int hashCode() { + return Objects.hash(candidateEvidence.getTitle(), user.getName(), rating); + } +} + diff --git a/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateCommentRatingKey.java b/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateCommentRatingKey.java deleted file mode 100644 index 5f15db8..0000000 --- a/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateCommentRatingKey.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.patternatlas.api.entities.candidate.rating; - -import java.io.Serializable; -import java.util.UUID; -import javax.persistence.Embeddable; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -@Embeddable -@NoArgsConstructor -@AllArgsConstructor -@Data -@EqualsAndHashCode(callSuper = false) -public class CandidateCommentRatingKey implements Serializable { - - protected UUID candidateCommentId; - protected UUID userId; -} diff --git a/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateRatingKey.java b/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateRatingKey.java deleted file mode 100644 index 6b8acc5..0000000 --- a/src/main/java/io/github/patternatlas/api/entities/candidate/rating/CandidateRatingKey.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.patternatlas.api.entities.candidate.rating; - -import java.io.Serializable; -import java.util.UUID; -import javax.persistence.Embeddable; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -@Embeddable -@NoArgsConstructor -@AllArgsConstructor -@Data -@EqualsAndHashCode(callSuper = false) -public class CandidateRatingKey implements Serializable { - - protected UUID candidateId; - protected UUID userId; -} diff --git a/src/main/java/io/github/patternatlas/api/entities/issue/Issue.java b/src/main/java/io/github/patternatlas/api/entities/issue/Issue.java index f0ab40b..99d44be 100644 --- a/src/main/java/io/github/patternatlas/api/entities/issue/Issue.java +++ b/src/main/java/io/github/patternatlas/api/entities/issue/Issue.java @@ -1,20 +1,19 @@ package io.github.patternatlas.api.entities.issue; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import javax.persistence.CascadeType; -import javax.persistence.Entity; -import javax.persistence.OneToMany; + +import io.github.patternatlas.api.entities.EntityWithURI; +import io.github.patternatlas.api.entities.issue.author.IssueAuthor; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidence; +import io.github.patternatlas.api.rest.model.issue.IssueModelRequest; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; - -import io.github.patternatlas.api.entities.issue.rating.IssueRating; -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.EntityWithURI; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.OneToMany; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -27,42 +26,44 @@ public class Issue extends EntityWithURI { private String description; - private int rating = 0; - private String version = "0.1.0"; + private int rating; + + @JsonIgnore + @OneToMany(mappedBy = "issue", cascade = CascadeType.ALL, orphanRemoval = true) + private List authors = new ArrayList<>(); + @JsonIgnore @OneToMany(mappedBy = "issue", cascade = CascadeType.ALL, orphanRemoval = true) - private Set userRating = new HashSet<>(); + private List userRating = new ArrayList<>(); @JsonProperty(access = JsonProperty.Access.READ_ONLY) @OneToMany(mappedBy = "issue", cascade = CascadeType.ALL, orphanRemoval = true) private List comments = new ArrayList<>(); - public void addComment(IssueComment comment, UserEntity user) { - comments.add(comment); - comment.setIssue(this); - comment.setUser(user); - } + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @OneToMany(mappedBy = "issue", cascade = CascadeType.ALL, orphanRemoval = true) + private List evidences = new ArrayList<>(); - public void updateComment(IssueComment updateComment) { - ListIterator commentIterator = comments.listIterator(); - while (commentIterator.hasNext()) { - IssueComment next = commentIterator.next(); - if (next.getId().equals(updateComment.getId())) { - commentIterator.set(updateComment); - break; - } - } + public Issue (IssueModelRequest issueModelRequest) { + this.setUri(issueModelRequest.getUri()); + this.setName(issueModelRequest.getName()); + this.setDescription(issueModelRequest.getDescription()); + this.setVersion(issueModelRequest.getVersion()); + this.setRating(issueModelRequest.getRating()); } - public void removeComment(IssueComment comment) { - comments.remove(comment); - comment.setIssue(null); - comment.setUser(null); + public void updateIssue(IssueModelRequest issueModelRequest) { + this.setUri(issueModelRequest.getUri()); + this.setName(issueModelRequest.getName()); + this.setDescription(issueModelRequest.getDescription()); + this.setVersion(issueModelRequest.getVersion()); + this.setRating(issueModelRequest.getRating()); } public String toString() { - return this.getId().toString() + this.getDescription(); + return this.getId().toString() + this.getDescription() + this.getComments().toString(); } -} + +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueRating.java b/src/main/java/io/github/patternatlas/api/entities/issue/IssueRating.java similarity index 66% rename from src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueRating.java rename to src/main/java/io/github/patternatlas/api/entities/issue/IssueRating.java index 14a4a80..38c46d3 100644 --- a/src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueRating.java +++ b/src/main/java/io/github/patternatlas/api/entities/issue/IssueRating.java @@ -1,13 +1,14 @@ -package io.github.patternatlas.api.entities.issue.rating; +package io.github.patternatlas.api.entities.issue; import java.util.Objects; + +import io.github.patternatlas.api.entities.shared.CompositeKey; +import io.github.patternatlas.api.entities.user.UserEntity; + import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.ManyToOne; import javax.persistence.MapsId; - -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.issue.Issue; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -19,10 +20,10 @@ public class IssueRating { @EmbeddedId @EqualsAndHashCode.Include - private IssueRatingKey id; + private CompositeKey id; @ManyToOne - @MapsId("issueId") + @MapsId("entityId") @EqualsAndHashCode.Include private Issue issue; @@ -36,7 +37,16 @@ public class IssueRating { public IssueRating(Issue issue, UserEntity user) { this.issue = issue; this.user = user; - this.id = new IssueRatingKey(issue.getId(), user.getId()); + this.id = new CompositeKey(issue.getId(), user.getId()); + } + + public IssueRating(Issue issue, UserEntity user, int rating) { + this(issue, user); + this.rating = rating; + } + + public void update(int rating) { + this.rating = rating; } @Override @@ -49,9 +59,8 @@ public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof IssueRating)) return false; IssueRating that = (IssueRating) o; - return Objects.equals(issue.getName(), that.issue.getName()) && - Objects.equals(user.getName(), that.user.getName()) && - Objects.equals(rating, that.rating); + return Objects.equals(issue.getId(), that.issue.getId()) && + Objects.equals(user.getId(), that.user.getId()); } @Override diff --git a/src/main/java/io/github/patternatlas/api/entities/issue/author/IssueAuthor.java b/src/main/java/io/github/patternatlas/api/entities/issue/author/IssueAuthor.java new file mode 100644 index 0000000..26f2000 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/issue/author/IssueAuthor.java @@ -0,0 +1,47 @@ +package io.github.patternatlas.api.entities.issue.author; + +import io.github.patternatlas.api.entities.issue.Issue; +import io.github.patternatlas.api.entities.shared.CompositeKey; +import io.github.patternatlas.api.entities.user.UserEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; + +@Entity +@Data +@NoArgsConstructor +public class IssueAuthor { + + @EmbeddedId + @EqualsAndHashCode.Include + private CompositeKey id; + + @ManyToOne + @MapsId("entityId") + @EqualsAndHashCode.Include + private Issue issue; + + @ManyToOne + @MapsId("userId") + @EqualsAndHashCode.Include + private UserEntity user; + + private String role; + + public IssueAuthor(Issue issue, UserEntity user) { + this.issue = issue; + this.user = user; + this.id = new CompositeKey(issue.getId(), user.getId()); + } + + public IssueAuthor(Issue issue, UserEntity user, String role) { + this(issue, user); + this.role = role; + } +} diff --git a/src/main/java/io/github/patternatlas/api/entities/issue/IssueComment.java b/src/main/java/io/github/patternatlas/api/entities/issue/comment/IssueComment.java similarity index 64% rename from src/main/java/io/github/patternatlas/api/entities/issue/IssueComment.java rename to src/main/java/io/github/patternatlas/api/entities/issue/comment/IssueComment.java index d023e79..a343681 100644 --- a/src/main/java/io/github/patternatlas/api/entities/issue/IssueComment.java +++ b/src/main/java/io/github/patternatlas/api/entities/issue/comment/IssueComment.java @@ -1,19 +1,19 @@ -package io.github.patternatlas.api.entities.issue; +package io.github.patternatlas.api.entities.issue.comment; import java.io.Serializable; -import java.util.HashSet; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; + +import io.github.patternatlas.api.entities.issue.Issue; +import io.github.patternatlas.api.entities.shared.Comment; +import io.github.patternatlas.api.entities.user.UserEntity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - -import io.github.patternatlas.api.entities.issue.rating.IssueCommentRating; -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.shared.Comment; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -30,16 +30,18 @@ public class IssueComment extends Comment implements Serializable { @ManyToOne private Issue issue; - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - @ToString.Exclude - @ManyToOne - private UserEntity user; - @JsonIgnore @OneToMany(mappedBy = "issueComment", cascade = CascadeType.ALL, orphanRemoval = true) - private Set userRating = new HashSet<>(); + private List userRating = new ArrayList<>(); + + private int rating = 0; + + public IssueComment(String text, Issue issue, UserEntity user) { + super(text, user); + this.issue = issue; + } - public IssueComment(String text) { - super(text); + public void updateComment(String text) { + this.setText(text); } } diff --git a/src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueCommentRating.java b/src/main/java/io/github/patternatlas/api/entities/issue/comment/IssueCommentRating.java similarity index 75% rename from src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueCommentRating.java rename to src/main/java/io/github/patternatlas/api/entities/issue/comment/IssueCommentRating.java index 4e84366..f671a90 100644 --- a/src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueCommentRating.java +++ b/src/main/java/io/github/patternatlas/api/entities/issue/comment/IssueCommentRating.java @@ -1,4 +1,4 @@ -package io.github.patternatlas.api.entities.issue.rating; +package io.github.patternatlas.api.entities.issue.comment; import java.util.Objects; import javax.persistence.EmbeddedId; @@ -7,7 +7,9 @@ import javax.persistence.MapsId; import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.issue.IssueComment; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; +import io.github.patternatlas.api.entities.shared.CompositeKey; + import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -19,10 +21,10 @@ public class IssueCommentRating { @EmbeddedId @EqualsAndHashCode.Include - private IssueCommentRatingKey id; + private CompositeKey id; @ManyToOne - @MapsId("issueCommentId") + @MapsId("entityId") @EqualsAndHashCode.Include private IssueComment issueComment; @@ -36,7 +38,12 @@ public class IssueCommentRating { public IssueCommentRating(IssueComment issueComment, UserEntity user) { this.issueComment = issueComment; this.user = user; - this.id = new IssueCommentRatingKey(issueComment.getId(), user.getId()); + this.id = new CompositeKey(issueComment.getId(), user.getId()); + } + + public IssueCommentRating(IssueComment issueComment, UserEntity user, int rating) { + this(issueComment, user); + this.rating = rating; } @Override diff --git a/src/main/java/io/github/patternatlas/api/entities/issue/evidence/IssueEvidence.java b/src/main/java/io/github/patternatlas/api/entities/issue/evidence/IssueEvidence.java new file mode 100644 index 0000000..bd7ba6c --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/issue/evidence/IssueEvidence.java @@ -0,0 +1,56 @@ +package io.github.patternatlas.api.entities.issue.evidence; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.github.patternatlas.api.entities.issue.Issue; +import io.github.patternatlas.api.entities.shared.Evidence; +import io.github.patternatlas.api.entities.user.UserEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class IssueEvidence extends Evidence implements Serializable { + + @JsonIgnore + @ToString.Exclude + @ManyToOne + private Issue issue; + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @ToString.Exclude + @ManyToOne + private UserEntity user; + + @JsonIgnore + @OneToMany(mappedBy = "issueEvidence", cascade = CascadeType.ALL, orphanRemoval = true) + private List userRating = new ArrayList<>(); + + public IssueEvidence(String title, String context, String type, Boolean supporting, String source, Issue issue, UserEntity user) { + super(title, context, type, supporting, source); + this.issue = issue; + this.user = user; + } + + public void updateEvidence(String title, String context, String type, Boolean supporting, String source) { + this.setTitle(title); + this.setContext(context); + this.setType(type); + this.setSupporting(supporting); + this.setSource(source); + } +} diff --git a/src/main/java/io/github/patternatlas/api/entities/issue/evidence/IssueEvidenceRating.java b/src/main/java/io/github/patternatlas/api/entities/issue/evidence/IssueEvidenceRating.java new file mode 100644 index 0000000..5173acf --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/issue/evidence/IssueEvidenceRating.java @@ -0,0 +1,70 @@ +package io.github.patternatlas.api.entities.issue.evidence; + +import io.github.patternatlas.api.entities.issue.Issue; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; +import io.github.patternatlas.api.entities.issue.comment.IssueCommentRating; +import io.github.patternatlas.api.entities.shared.CompositeKey; +import io.github.patternatlas.api.entities.user.UserEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import java.util.Objects; + +@Entity +@Data +@NoArgsConstructor +public class IssueEvidenceRating { + + @EmbeddedId + @EqualsAndHashCode.Include + private CompositeKey id; + + @ManyToOne + @MapsId("entityId") + @EqualsAndHashCode.Include + private IssueEvidence issueEvidence; + + @ManyToOne + @MapsId("userId") + @EqualsAndHashCode.Include + private UserEntity user; + + private int rating; + + public IssueEvidenceRating(IssueEvidence issueEvidence, UserEntity user) { + this.issueEvidence = issueEvidence; + this.user = user; + this.id = new CompositeKey(issueEvidence.getId(), user.getId()); + } + + public IssueEvidenceRating(IssueEvidence issueEvidence, UserEntity user, int rating) { + this(issueEvidence, user); + this.rating = rating; + } + + @Override + public String toString() { + return this.id.toString() + this.rating; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof IssueEvidenceRating)) return false; + IssueEvidenceRating that = (IssueEvidenceRating) o; + return Objects.equals(issueEvidence.getTitle(), that.issueEvidence.getTitle()) && + Objects.equals(user.getName(), that.user.getName()) && + Objects.equals(rating, that.rating); + } + + @Override + public int hashCode() { + return Objects.hash(issueEvidence.getTitle(), user.getName(), rating); + } +} diff --git a/src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueCommentRatingKey.java b/src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueCommentRatingKey.java deleted file mode 100644 index c635bcf..0000000 --- a/src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueCommentRatingKey.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.patternatlas.api.entities.issue.rating; - -import java.io.Serializable; -import java.util.UUID; -import javax.persistence.Embeddable; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -@Embeddable -@NoArgsConstructor -@AllArgsConstructor -@Data -@EqualsAndHashCode(callSuper = false) -public class IssueCommentRatingKey implements Serializable { - - protected UUID issueCommentId; - protected UUID userId; -} diff --git a/src/main/java/io/github/patternatlas/api/entities/shared/AuthorConstant.java b/src/main/java/io/github/patternatlas/api/entities/shared/AuthorConstant.java new file mode 100644 index 0000000..683ab33 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/shared/AuthorConstant.java @@ -0,0 +1,7 @@ +package io.github.patternatlas.api.entities.shared; + +public interface AuthorConstant { + String OWNER = "OWNER"; + String MAINTAINER = "MAINTAINER"; + String HELPER = "HELPER"; +} diff --git a/src/main/java/io/github/patternatlas/api/entities/shared/Comment.java b/src/main/java/io/github/patternatlas/api/entities/shared/Comment.java index 0cf62b8..86bfc8a 100644 --- a/src/main/java/io/github/patternatlas/api/entities/shared/Comment.java +++ b/src/main/java/io/github/patternatlas/api/entities/shared/Comment.java @@ -2,12 +2,18 @@ import java.util.Objects; import java.util.UUID; + +import io.github.patternatlas.api.entities.user.UserEntity; + +import com.fasterxml.jackson.annotation.JsonProperty; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.ManyToOne; import javax.persistence.MappedSuperclass; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.ToString; @MappedSuperclass @Data @@ -20,10 +26,14 @@ public abstract class Comment { private String text; - private int rating = 0; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @ToString.Exclude + @ManyToOne + private UserEntity user; - public Comment(String text) { + public Comment(String text, UserEntity user) { this.text = text; + this.user = user; } @Override @@ -32,7 +42,8 @@ public boolean equals(Object o) { if (!(o instanceof Comment)) return false; Comment that = (Comment) o; return id.equals(that.id) && - text.equals(that.text); + text.equals(that.text) && + user.equals(that.user); } @Override @@ -42,6 +53,6 @@ public int hashCode() { @Override public String toString() { - return "Comment: " + this.text + this.id.toString() + this.rating; + return "Comment: " + this.text + this.id.toString(); } } diff --git a/src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueRatingKey.java b/src/main/java/io/github/patternatlas/api/entities/shared/CompositeKey.java similarity index 70% rename from src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueRatingKey.java rename to src/main/java/io/github/patternatlas/api/entities/shared/CompositeKey.java index 9522921..b62b247 100644 --- a/src/main/java/io/github/patternatlas/api/entities/issue/rating/IssueRatingKey.java +++ b/src/main/java/io/github/patternatlas/api/entities/shared/CompositeKey.java @@ -1,4 +1,4 @@ -package io.github.patternatlas.api.entities.issue.rating; +package io.github.patternatlas.api.entities.shared; import java.io.Serializable; import java.util.UUID; @@ -14,8 +14,8 @@ @AllArgsConstructor @Data @EqualsAndHashCode(callSuper = false) -public class IssueRatingKey implements Serializable { +public class CompositeKey implements Serializable { - protected UUID issueId; + protected UUID entityId; protected UUID userId; } diff --git a/src/main/java/io/github/patternatlas/api/entities/shared/Evidence.java b/src/main/java/io/github/patternatlas/api/entities/shared/Evidence.java new file mode 100644 index 0000000..8e5ae3d --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/shared/Evidence.java @@ -0,0 +1,53 @@ +package io.github.patternatlas.api.entities.shared; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import java.util.Objects; +import java.util.UUID; + +@MappedSuperclass +@Data +@NoArgsConstructor +public abstract class Evidence { + + @Id + @GeneratedValue(generator = "pg-uuid") + private UUID id; + + private String title; + private String context; + private String type; + private Boolean supporting; + private String source; + + public Evidence(String title, String context, String type, Boolean supporting, String source) { + this.title = title; + this.context = context; + this.type = type; + this.supporting = supporting; + this.source = source; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Evidence)) return false; + Evidence that = (Evidence) o; + return id.equals(that.id) && + title.equals(that.title); + } + + @Override + public int hashCode() { + return Objects.hash(id, title); + } + + @Override + public String toString() { + return "Evidence: " + this.title + this.id.toString(); + } +} diff --git a/src/main/java/io/github/patternatlas/api/entities/user/UserEntity.java b/src/main/java/io/github/patternatlas/api/entities/user/UserEntity.java index 679cb31..dc5f693 100644 --- a/src/main/java/io/github/patternatlas/api/entities/user/UserEntity.java +++ b/src/main/java/io/github/patternatlas/api/entities/user/UserEntity.java @@ -2,59 +2,63 @@ import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.UUID; + import javax.persistence.CascadeType; import javax.persistence.Column; -import javax.persistence.ElementCollection; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.ManyToMany; import javax.persistence.OneToMany; - +import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.NaturalId; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType; -import io.github.patternatlas.api.entities.candidate.CandidateComment; -import io.github.patternatlas.api.entities.candidate.rating.CandidateRating; -import io.github.patternatlas.api.entities.issue.IssueComment; -import io.github.patternatlas.api.entities.issue.rating.IssueRating; +import io.github.patternatlas.api.entities.candidate.CandidateRating; +import io.github.patternatlas.api.entities.candidate.comment.CandidateComment; +import io.github.patternatlas.api.entities.candidate.author.CandidateAuthor; +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidence; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; +import io.github.patternatlas.api.entities.issue.author.IssueAuthor; +import io.github.patternatlas.api.entities.issue.IssueRating; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidence; +import io.github.patternatlas.api.entities.user.role.Role; +import io.github.patternatlas.api.rest.model.user.UserModel; + import lombok.Data; import lombok.NoArgsConstructor; +import lombok.ToString; @Entity @Data @NoArgsConstructor -@TypeDef(name = "pgsql_enum", typeClass = PostgreSQLEnumType.class) -public class UserEntity implements Serializable { +public class UserEntity implements Serializable{ /** * User fields */ @Id - @GeneratedValue(generator = "pg-uuid") + @GenericGenerator(name = "UserIdGenerator", strategy = "io.github.patternatlas.api.util.UserIdGenerator") + @GeneratedValue(generator = "UserIdGenerator") private UUID id; - @Enumerated(EnumType.STRING) - @ElementCollection - @Type(type = "pgsql_enum") - private List roles = new ArrayList<>(Arrays.asList(UserRole.MEMBER)); + @JsonIgnore + @ToString.Exclude + @ManyToMany() + private Set roles; - @NaturalId(mutable = true) - @Column(nullable = false, unique = true) + @Column(nullable = false, unique = false) private String email; + @NaturalId(mutable = true) + @Column(nullable = false, unique = true) private String name; @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) @@ -63,6 +67,10 @@ public class UserEntity implements Serializable { /** * Issue fields */ + @JsonIgnore + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private Set issues = new HashSet<>(); + @JsonIgnore @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private Set issueRatings = new HashSet<>(); @@ -71,9 +79,15 @@ public class UserEntity implements Serializable { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List issueComments = new ArrayList<>(); - /** - * Candidate fields - */ + @JsonIgnore + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List issueEvidence = new ArrayList<>(); + + /** Candidate fields*/ + @JsonIgnore + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private Set candidates = new HashSet<>(); + @JsonIgnore @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private Set candidateRatings = new HashSet<>(); @@ -82,27 +96,31 @@ public class UserEntity implements Serializable { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List candidateComments = new ArrayList<>(); - /** - * Pattern fields - */ -// @JsonIgnore -// @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) -// private Set candidateRatings = new HashSet<>(); -// -// @JsonIgnore -// @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) -// private List candidateComments = new ArrayList<>(); - public UserEntity(String name, String email, String password) { - this.name = name; - this.email = email; - this.password = password; + @JsonIgnore + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List candidateEvidence = new ArrayList<>(); + + /** Pattern fields*/ + + public UserEntity(UserModel userModel, String password) { + this(userModel.getName(), userModel.getEmail(), password, null); + this.id = userModel.getId(); } - public UserEntity(String name, String email, String password, List roles) { + public UserEntity(String name, String email, String password, Set roles) { this.name = name; this.email = email; this.password = password; this.roles = roles; + + if(this.roles == null) { + this.roles = new HashSet<>(); + } + } + + public void updateUserEntity(UserModel userModel) { + this.setName(userModel.getName()); + this.setEmail(userModel.getEmail()); } @Override @@ -124,4 +142,13 @@ public int hashCode() { public String toString() { return "User: " + this.name; } + + public void removeRole(String roleName) { + for (Role role : this.roles) { + if (role.getName().contains(roleName)) { + this.roles.remove(role); + return; + } + } + } } diff --git a/src/main/java/io/github/patternatlas/api/entities/user/UserRole.java b/src/main/java/io/github/patternatlas/api/entities/user/UserRole.java deleted file mode 100644 index de7a4e7..0000000 --- a/src/main/java/io/github/patternatlas/api/entities/user/UserRole.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.patternatlas.api.entities.user; - -public enum UserRole { - MEMBER, - EXPERT, - AUTHOR, - LIBRARIAN, - ADMIN -} diff --git a/src/main/java/io/github/patternatlas/api/entities/user/role/Privilege.java b/src/main/java/io/github/patternatlas/api/entities/user/role/Privilege.java new file mode 100644 index 0000000..fdc90ff --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/user/role/Privilege.java @@ -0,0 +1,52 @@ +package io.github.patternatlas.api.entities.user.role; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Collection; +import java.util.UUID; + +@Entity +@Data +@NoArgsConstructor +public class Privilege { + + @Id + @GeneratedValue(generator = "pg-uuid") + private UUID id; + + @Column(unique = true) + private String name; + + @ManyToMany(mappedBy = "privileges") + private Collection roles; + + public Privilege(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Privilege)) return false; + Privilege that = (Privilege) o; + return id.equals(that.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String toString() { + return "Privileges: " + this.name; + } + + +} diff --git a/src/main/java/io/github/patternatlas/api/entities/user/role/PrivilegeConstant.java b/src/main/java/io/github/patternatlas/api/entities/user/role/PrivilegeConstant.java new file mode 100644 index 0000000..cfec2a8 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/user/role/PrivilegeConstant.java @@ -0,0 +1,72 @@ +package io.github.patternatlas.api.entities.user.role; + +import org.springframework.stereotype.Component; + +@Component("PC") +public class PrivilegeConstant { + /** ISSUE */ + public static final String ISSUE_READ = "ISSUE_READ"; + public static final String ISSUE_EDIT = "ISSUE_EDIT"; + public static final String ISSUE_DELETE = "ISSUE_DELETE"; + public static final String ISSUE_COMMENT = "ISSUE_COMMENT"; + public static final String ISSUE_VOTE = "ISSUE_VOTE"; + public static final String ISSUE_EVIDENCE = "ISSUE_EVIDENCE"; + public static final String ISSUE_TO_PATTERN_CANDIDATE = "ISSUE_TO_PATTERN_CANDIDATE"; + public static final String ISSUE_CREATE = "ISSUE_CREATE"; + public static final String ISSUE_READ_ALL = "ISSUE_READ_ALL"; + public static final String ISSUE_EDIT_ALL = "ISSUE_EDIT_ALL"; + public static final String ISSUE_DELETE_ALL = "ISSUE_DELETE_ALL"; + public static final String ISSUE_COMMENT_ALL = "ISSUE_COMMENT_ALL"; + public static final String ISSUE_VOTE_ALL = "ISSUE_VOTE_ALL"; + public static final String ISSUE_EVIDENCE_ALL = "ISSUE_EVIDENCE_ALL"; + public static final String ISSUE_TO_PATTERN_CANDIDATE_ALL = "ISSUE_TO_PATTERN_CANDIDATE_ALL"; + /** CANDIDATE */ + public static final String PATTERN_CANDIDATE_READ = "PATTERN_CANDIDATE_READ"; + public static final String PATTERN_CANDIDATE_EDIT = "PATTERN_CANDIDATE_EDIT"; + public static final String PATTERN_CANDIDATE_DELETE = "PATTERN_CANDIDATE_DELETE"; + public static final String PATTERN_CANDIDATE_COMMENT = "PATTERN_CANDIDATE_COMMENT"; + public static final String PATTERN_CANDIDATE_VOTE = "PATTERN_CANDIDATE_VOTE"; + public static final String PATTERN_CANDIDATE_EVIDENCE = "PATTERN_CANDIDATE_EVIDENCE"; + public static final String PATTERN_CANDIDATE_TO_PATTERN = "PATTERN_CANDIDATE_TO_PATTERN"; + public static final String PATTERN_CANDIDATE_CREATE = "PATTERN_CANDIDATE_CREATE"; + public static final String PATTERN_CANDIDATE_READ_ALL = "PATTERN_CANDIDATE_READ_ALL"; + public static final String PATTERN_CANDIDATE_EDIT_ALL = "PATTERN_CANDIDATE_EDIT_ALL"; + public static final String PATTERN_CANDIDATE_DELETE_ALL = "PATTERN_CANDIDATE_DELETE_ALL"; + public static final String PATTERN_CANDIDATE_COMMENT_ALL = "PATTERN_CANDIDATE_COMMENT_ALL"; + public static final String PATTERN_CANDIDATE_VOTE_ALL = "PATTERN_CANDIDATE_VOTE_ALL"; + public static final String PATTERN_CANDIDATE_EVIDENCE_ALL = "PATTERN_CANDIDATE_EVIDENCE_ALL"; + public static final String PATTERN_CANDIDATE_TO_PATTERN_ALL = "PATTERN_CANDIDATE_TO_PATTERN_ALL"; + /** Pattern Language */ + public static final String PATTERN_LANGUAGE_READ = "PATTERN_LANGUAGE_READ"; + public static final String PATTERN_LANGUAGE_EDIT = "PATTERN_LANGUAGE_EDIT"; + public static final String PATTERN_LANGUAGE_DELETE = "PATTERN_LANGUAGE_DELETE"; + public static final String PATTERN_LANGUAGE_CREATE = "PATTERN_LANGUAGE_CREATE"; + public static final String PATTERN_LANGUAGE_READ_ALL = "PATTERN_LANGUAGE_READ_ALL"; + public static final String PATTERN_LANGUAGE_EDIT_ALL = "PATTERN_LANGUAGE_EDIT_ALL"; + public static final String PATTERN_LANGUAGE_DELETE_ALL = "PATTERN_LANGUAGE_DELETE_ALL"; + /** Pattern */ + public static final String APPROVED_PATTERN_READ = "APPROVED_PATTERN_READ"; + public static final String APPROVED_PATTERN_EDIT = "APPROVED_PATTERN_EDIT"; + public static final String APPROVED_PATTERN_DELETE = "APPROVED_PATTERN_DELETE"; + public static final String APPROVED_PATTERN_CREATE = "APPROVED_PATTERN_CREATE"; + public static final String APPROVED_PATTERN_READ_ALL = "APPROVED_PATTERN_READ_ALL"; + public static final String APPROVED_PATTERN_EDIT_ALL = "APPROVED_PATTERN_EDIT_ALL"; + public static final String APPROVED_PATTERN_DELETE_ALL = "APPROVED_PATTERN_DELETE_ALL"; + /** USER */ + public static final String USER_READ = "USER_READ"; + public static final String USER_EDIT = "USER_EDIT"; + public static final String USER_DELETE = "USER_DELETE"; + public static final String USER_CREATE = "USER_CREATE"; + public static final String USER_READ_ALL = "USER_READ_ALL"; + public static final String USER_EDIT_ALL = "USER_EDIT_ALL"; + public static final String USER_DELETE_ALL = "USER_DELETE_ALL"; + public static final String USER_ALL = "USER_ALL"; + /** Pattern View */ + public static final String PATTERN_VIEW_READ = "PATTERN_VIEW_READ"; + public static final String PATTERN_VIEW_EDIT = "PATTERN_VIEW_EDIT"; + public static final String PATTERN_VIEW_DELETE = "PATTERN_VIEW_DELETE"; + public static final String PATTERN_VIEW_CREATE = "PATTERN_VIEW_CREATE"; + public static final String PATTERN_VIEW_READ_ALL = "PATTERN_VIEW_READ_ALL"; + public static final String PATTERN_VIEW_EDIT_ALL = "PATTERN_VIEW_EDIT_ALL"; + public static final String PATTERN_VIEW_DELETE_ALL = "PATTERN_VIEW_DELETE_ALL"; +} diff --git a/src/main/java/io/github/patternatlas/api/entities/user/role/Role.java b/src/main/java/io/github/patternatlas/api/entities/user/role/Role.java new file mode 100644 index 0000000..c265955 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/user/role/Role.java @@ -0,0 +1,59 @@ +package io.github.patternatlas.api.entities.user.role; + +import io.github.patternatlas.api.entities.user.UserEntity; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Set; +import java.util.UUID; +import java.util.Collection; + +@Entity +@Data +@NoArgsConstructor +public class Role { + + @Id + @GeneratedValue(generator = "pg-uuid") + private UUID id; + + @Column(unique = true) + private String name; + + @ManyToMany(mappedBy = "roles") + private Set users; + + @ManyToMany(cascade = CascadeType.ALL) + private Collection privileges; + + public Role(String name) { + this.name = name; + } + + public boolean checkPrivilege(String privilege) { + for (Privilege p : this.privileges) { + if (p.getName().equals(privilege)) { + return true; + } + } + return false; + } + + public void removePrivilege(Privilege privilege) { + this.privileges.remove(privilege); + privilege.getRoles().remove(this); + } + + public void addPrivilege(Privilege privilege) { + this.privileges.add(privilege); + privilege.getRoles().add(this); + } + +} diff --git a/src/main/java/io/github/patternatlas/api/entities/user/role/RoleConstant.java b/src/main/java/io/github/patternatlas/api/entities/user/role/RoleConstant.java new file mode 100644 index 0000000..37400e6 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/entities/user/role/RoleConstant.java @@ -0,0 +1,32 @@ +package io.github.patternatlas.api.entities.user.role; + +import java.util.Arrays; +import java.util.List; + +public class RoleConstant { + public static final String MEMBER = "MEMBER"; + public static final String HELPER = "HELPER"; + public static final String MAINTAINER = "MAINTAINER"; + public static final String OWNER = "OWNER"; + public static final String EXPERT = "EXPERT"; + public static final String LIBRARIAN = "LIBRARIAN"; + public static final String ADMIN = "ADMIN"; + public static final String DEVELOPER = "DEVELOPER"; + public static final String GUEST = "GUEST"; + + public static List PLATFORM_ROLES = Arrays.asList( + RoleConstant.ADMIN, + RoleConstant.MEMBER, + RoleConstant.EXPERT, + RoleConstant.LIBRARIAN, + RoleConstant.GUEST + ); + + public static List AUTHOR_ROLES = Arrays.asList( + RoleConstant.HELPER, + RoleConstant.MAINTAINER, + RoleConstant.OWNER + ); + +} + diff --git a/src/main/java/io/github/patternatlas/api/exception/CandidateNotFoundException.java b/src/main/java/io/github/patternatlas/api/exception/CandidateNotFoundException.java deleted file mode 100644 index badfb7f..0000000 --- a/src/main/java/io/github/patternatlas/api/exception/CandidateNotFoundException.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.patternatlas.api.exception; - -import java.util.UUID; - -import org.springframework.data.rest.webmvc.ResourceNotFoundException; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@ResponseStatus(HttpStatus.NOT_FOUND) -public class CandidateNotFoundException extends ResourceNotFoundException { - - public CandidateNotFoundException(String message) { - super(message); - } - - public CandidateNotFoundException(UUID candidateId) { - super(String.format("Candidate \"%s\" not found!", candidateId)); - } -} diff --git a/src/main/java/io/github/patternatlas/api/exception/CommentNotFoundException.java b/src/main/java/io/github/patternatlas/api/exception/CommentNotFoundException.java deleted file mode 100644 index e131790..0000000 --- a/src/main/java/io/github/patternatlas/api/exception/CommentNotFoundException.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.patternatlas.api.exception; - -import java.util.UUID; - -import org.springframework.data.rest.webmvc.ResourceNotFoundException; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@ResponseStatus(HttpStatus.NOT_FOUND) -public class CommentNotFoundException extends ResourceNotFoundException { - - public CommentNotFoundException(String message) { - super(message); - } - - public CommentNotFoundException(UUID commentId) { - super(String.format("Comment \"%s\" not found!", commentId)); - } -} diff --git a/src/main/java/io/github/patternatlas/api/exception/NullIssueException.java b/src/main/java/io/github/patternatlas/api/exception/NullIssueException.java deleted file mode 100644 index 0de5244..0000000 --- a/src/main/java/io/github/patternatlas/api/exception/NullIssueException.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.patternatlas.api.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@ResponseStatus(HttpStatus.NOT_FOUND) -public class NullIssueException extends RuntimeException { - public NullIssueException(String message) { - super(message); - } -} diff --git a/src/main/java/io/github/patternatlas/api/exception/NullRoleException.java b/src/main/java/io/github/patternatlas/api/exception/NullRoleException.java new file mode 100644 index 0000000..48d00e9 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/exception/NullRoleException.java @@ -0,0 +1,12 @@ +package io.github.patternatlas.api.exception; + +public class NullRoleException extends RuntimeException { + + public NullRoleException() { + super("Role is null"); + } + + public NullRoleException(String message) { + super(message); + } +} diff --git a/src/main/java/io/github/patternatlas/api/exception/IssueNotFoundException.java b/src/main/java/io/github/patternatlas/api/exception/RoleNotFoundException.java similarity index 61% rename from src/main/java/io/github/patternatlas/api/exception/IssueNotFoundException.java rename to src/main/java/io/github/patternatlas/api/exception/RoleNotFoundException.java index c5c6b17..d29a65f 100644 --- a/src/main/java/io/github/patternatlas/api/exception/IssueNotFoundException.java +++ b/src/main/java/io/github/patternatlas/api/exception/RoleNotFoundException.java @@ -10,13 +10,13 @@ @NoArgsConstructor @ResponseStatus(HttpStatus.NOT_FOUND) -public class IssueNotFoundException extends ResourceNotFoundException { +public class RoleNotFoundException extends ResourceNotFoundException { - public IssueNotFoundException(String message) { + public RoleNotFoundException(String message) { super(message); } - public IssueNotFoundException(UUID issueId) { - super(String.format("Issue \"%s\" not found!", issueId)); + public RoleNotFoundException(UUID roleId) { + super(String.format("Role \"%s\" not found!", roleId)); } } diff --git a/src/main/java/io/github/patternatlas/api/exception/UserAlreadyVotedException.java b/src/main/java/io/github/patternatlas/api/exception/UserAlreadyVotedException.java deleted file mode 100644 index 7548861..0000000 --- a/src/main/java/io/github/patternatlas/api/exception/UserAlreadyVotedException.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.github.patternatlas.api.exception; - -import java.util.UUID; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -import io.github.patternatlas.api.entities.PatternView; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@ResponseStatus(HttpStatus.NOT_FOUND) -public class UserAlreadyVotedException extends RuntimeException { - - public UserAlreadyVotedException(String message) { - super(message); - } - - public UserAlreadyVotedException(UUID patternViewId) { - super(String.format("PatternView \"%s\" not found!", patternViewId)); - } - - public UserAlreadyVotedException(PatternView patternView) { - super(String.format("PatternView \"%s\" not found!", patternView.getId())); - } -} diff --git a/src/main/java/io/github/patternatlas/api/repositories/CandidateAuthorRepository.java b/src/main/java/io/github/patternatlas/api/repositories/CandidateAuthorRepository.java new file mode 100644 index 0000000..e8a6eb2 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/repositories/CandidateAuthorRepository.java @@ -0,0 +1,12 @@ +package io.github.patternatlas.api.repositories; + +import io.github.patternatlas.api.entities.candidate.author.CandidateAuthor; +import io.github.patternatlas.api.entities.shared.CompositeKey; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CandidateAuthorRepository extends JpaRepository { + + boolean existsByIdAndRole(CompositeKey compositeKey, String role); + +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/repositories/CandidateCommentRatingRepository.java b/src/main/java/io/github/patternatlas/api/repositories/CandidateCommentRatingRepository.java index 82ac2b9..d9e1d26 100644 --- a/src/main/java/io/github/patternatlas/api/repositories/CandidateCommentRatingRepository.java +++ b/src/main/java/io/github/patternatlas/api/repositories/CandidateCommentRatingRepository.java @@ -1,15 +1,13 @@ package io.github.patternatlas.api.repositories; +import io.github.patternatlas.api.entities.candidate.comment.CandidateCommentRating; +import io.github.patternatlas.api.entities.shared.CompositeKey; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; -import io.github.patternatlas.api.entities.candidate.CandidateComment; -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.candidate.rating.CandidateCommentRating; -import io.github.patternatlas.api.entities.candidate.rating.CandidateCommentRatingKey; - @RepositoryRestResource(exported = false) -public interface CandidateCommentRatingRepository extends JpaRepository { +public interface CandidateCommentRatingRepository extends JpaRepository { - CandidateCommentRating findByCandidateCommentAndUser(CandidateComment candidateComment, UserEntity user); + boolean existsByIdAndRating(CompositeKey compositeKey, int rating); } diff --git a/src/main/java/io/github/patternatlas/api/repositories/CandidateCommentRepository.java b/src/main/java/io/github/patternatlas/api/repositories/CandidateCommentRepository.java index 04b5249..c397c67 100644 --- a/src/main/java/io/github/patternatlas/api/repositories/CandidateCommentRepository.java +++ b/src/main/java/io/github/patternatlas/api/repositories/CandidateCommentRepository.java @@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; -import io.github.patternatlas.api.entities.candidate.CandidateComment; +import io.github.patternatlas.api.entities.candidate.comment.CandidateComment; @RepositoryRestResource(exported = false) public interface CandidateCommentRepository extends JpaRepository { diff --git a/src/main/java/io/github/patternatlas/api/repositories/CandidateEvidenceRatingRepository.java b/src/main/java/io/github/patternatlas/api/repositories/CandidateEvidenceRatingRepository.java new file mode 100644 index 0000000..772880b --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/repositories/CandidateEvidenceRatingRepository.java @@ -0,0 +1,11 @@ +package io.github.patternatlas.api.repositories; + +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidenceRating; +import io.github.patternatlas.api.entities.shared.CompositeKey; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CandidateEvidenceRatingRepository extends JpaRepository { + + boolean existsByIdAndRating(CompositeKey compositeKey, int rating); +} diff --git a/src/main/java/io/github/patternatlas/api/repositories/CandidateEvidenceRepository.java b/src/main/java/io/github/patternatlas/api/repositories/CandidateEvidenceRepository.java new file mode 100644 index 0000000..256a265 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/repositories/CandidateEvidenceRepository.java @@ -0,0 +1,9 @@ +package io.github.patternatlas.api.repositories; + +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidence; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface CandidateEvidenceRepository extends JpaRepository { +} diff --git a/src/main/java/io/github/patternatlas/api/repositories/CandidateRatingRepository.java b/src/main/java/io/github/patternatlas/api/repositories/CandidateRatingRepository.java index a948481..60987bb 100644 --- a/src/main/java/io/github/patternatlas/api/repositories/CandidateRatingRepository.java +++ b/src/main/java/io/github/patternatlas/api/repositories/CandidateRatingRepository.java @@ -1,21 +1,16 @@ package io.github.patternatlas.api.repositories; -import java.util.List; +import io.github.patternatlas.api.entities.candidate.CandidateRating; +import io.github.patternatlas.api.entities.shared.CompositeKey; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.candidate.Candidate; -import io.github.patternatlas.api.entities.candidate.rating.CandidateRating; -import io.github.patternatlas.api.entities.candidate.rating.CandidateRatingKey; - @RepositoryRestResource(exported = false) -public interface CandidateRatingRepository extends JpaRepository { - - List findAllByCandidate(Candidate candidate); - - List findAllByUser(UserEntity user); +public interface CandidateRatingRepository extends JpaRepository { - CandidateRating findByCandidateAndUser(Candidate candidate, UserEntity user); -} + boolean existsById(CompositeKey compositeKey); + boolean existsByIdAndReadability(CompositeKey compositeKey, int rating); + boolean existsByIdAndUnderstandability(CompositeKey compositeKey, int rating); + boolean existsByIdAndAppropriateness(CompositeKey compositeKey, int rating); +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/repositories/CandidateRepository.java b/src/main/java/io/github/patternatlas/api/repositories/CandidateRepository.java index 44ac023..5c3f78a 100644 --- a/src/main/java/io/github/patternatlas/api/repositories/CandidateRepository.java +++ b/src/main/java/io/github/patternatlas/api/repositories/CandidateRepository.java @@ -2,8 +2,11 @@ import java.util.Optional; import java.util.UUID; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; import io.github.patternatlas.api.entities.candidate.Candidate; @@ -11,7 +14,10 @@ @RepositoryRestResource(exported = false) public interface CandidateRepository extends JpaRepository { - Optional findByUri(String uri); + public Optional findByUri(String uri); + public boolean existsByUri(String uri); + public boolean existsByName(String name); - boolean existsByUri(String uri); + @Query(value = "SELECT * FROM candidate c WHERE c.pattern_language_id = :languageId", nativeQuery = true) + public List findAllByLanguageId(@Param("languageId") UUID languageId); } diff --git a/src/main/java/io/github/patternatlas/api/repositories/IssueAuthorRepository.java b/src/main/java/io/github/patternatlas/api/repositories/IssueAuthorRepository.java new file mode 100644 index 0000000..f26d18d --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/repositories/IssueAuthorRepository.java @@ -0,0 +1,12 @@ +package io.github.patternatlas.api.repositories; + +import io.github.patternatlas.api.entities.issue.author.IssueAuthor; +import io.github.patternatlas.api.entities.shared.CompositeKey; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface IssueAuthorRepository extends JpaRepository { + + boolean existsByIdAndRole(CompositeKey compositeKey, String role); + +} diff --git a/src/main/java/io/github/patternatlas/api/repositories/IssueCommentRatingRepository.java b/src/main/java/io/github/patternatlas/api/repositories/IssueCommentRatingRepository.java index efa9705..9eb2ed1 100644 --- a/src/main/java/io/github/patternatlas/api/repositories/IssueCommentRatingRepository.java +++ b/src/main/java/io/github/patternatlas/api/repositories/IssueCommentRatingRepository.java @@ -3,13 +3,16 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; -import io.github.patternatlas.api.entities.issue.rating.IssueCommentRating; -import io.github.patternatlas.api.entities.issue.rating.IssueCommentRatingKey; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; +import io.github.patternatlas.api.entities.issue.comment.IssueCommentRating; +import io.github.patternatlas.api.entities.shared.CompositeKey; import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.issue.IssueComment; @RepositoryRestResource(exported = false) -public interface IssueCommentRatingRepository extends JpaRepository { +public interface IssueCommentRatingRepository extends JpaRepository { IssueCommentRating findByIssueCommentAndUser(IssueComment issueComment, UserEntity user); + + boolean existsByIdAndRating(CompositeKey compositeKey, int rating); + } diff --git a/src/main/java/io/github/patternatlas/api/repositories/IssueCommentRepository.java b/src/main/java/io/github/patternatlas/api/repositories/IssueCommentRepository.java index 3d99658..f343bef 100644 --- a/src/main/java/io/github/patternatlas/api/repositories/IssueCommentRepository.java +++ b/src/main/java/io/github/patternatlas/api/repositories/IssueCommentRepository.java @@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; -import io.github.patternatlas.api.entities.issue.IssueComment; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; @RepositoryRestResource(exported = false) public interface IssueCommentRepository extends JpaRepository { diff --git a/src/main/java/io/github/patternatlas/api/repositories/IssueEvidenceRatingRepository.java b/src/main/java/io/github/patternatlas/api/repositories/IssueEvidenceRatingRepository.java new file mode 100644 index 0000000..0486604 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/repositories/IssueEvidenceRatingRepository.java @@ -0,0 +1,11 @@ +package io.github.patternatlas.api.repositories; + +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidenceRating; +import io.github.patternatlas.api.entities.shared.CompositeKey; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface IssueEvidenceRatingRepository extends JpaRepository { + + boolean existsByIdAndRating(CompositeKey compositeKey, int rating); +} diff --git a/src/main/java/io/github/patternatlas/api/repositories/IssueEvidenceRepository.java b/src/main/java/io/github/patternatlas/api/repositories/IssueEvidenceRepository.java new file mode 100644 index 0000000..9075312 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/repositories/IssueEvidenceRepository.java @@ -0,0 +1,12 @@ +package io.github.patternatlas.api.repositories; + +import io.github.patternatlas.api.entities.issue.comment.IssueComment; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidence; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface IssueEvidenceRepository extends JpaRepository { + +} diff --git a/src/main/java/io/github/patternatlas/api/repositories/IssueRatingRepository.java b/src/main/java/io/github/patternatlas/api/repositories/IssueRatingRepository.java index d3122dc..067c1a2 100644 --- a/src/main/java/io/github/patternatlas/api/repositories/IssueRatingRepository.java +++ b/src/main/java/io/github/patternatlas/api/repositories/IssueRatingRepository.java @@ -1,21 +1,17 @@ package io.github.patternatlas.api.repositories; -import java.util.List; +import io.github.patternatlas.api.entities.issue.Issue; +import io.github.patternatlas.api.entities.issue.IssueRating; +import io.github.patternatlas.api.entities.shared.CompositeKey; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; -import io.github.patternatlas.api.entities.issue.rating.IssueRating; -import io.github.patternatlas.api.entities.issue.rating.IssueRatingKey; -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.issue.Issue; +import java.util.Collection; @RepositoryRestResource(exported = false) -public interface IssueRatingRepository extends JpaRepository { - - List findAllByIssue(Issue issue); - - List findAllByUser(UserEntity user); +public interface IssueRatingRepository extends JpaRepository { - IssueRating findByIssueAndUser(Issue issue, UserEntity user); -} + boolean existsByIdAndRating(CompositeKey compositeKey, int rating); + Collection findAllByIssue(Issue issue); +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/repositories/IssueRepository.java b/src/main/java/io/github/patternatlas/api/repositories/IssueRepository.java index b867e5b..1970c5d 100644 --- a/src/main/java/io/github/patternatlas/api/repositories/IssueRepository.java +++ b/src/main/java/io/github/patternatlas/api/repositories/IssueRepository.java @@ -11,7 +11,7 @@ @RepositoryRestResource(exported = false) public interface IssueRepository extends JpaRepository { - Optional findByUri(String uri); - - boolean existsByUri(String uri); + public Optional findByUri(String uri); + public boolean existsByUri(String uri); + public boolean existsByName(String name); } diff --git a/src/main/java/io/github/patternatlas/api/repositories/PrivilegeRepository.java b/src/main/java/io/github/patternatlas/api/repositories/PrivilegeRepository.java new file mode 100644 index 0000000..fe14991 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/repositories/PrivilegeRepository.java @@ -0,0 +1,40 @@ +package io.github.patternatlas.api.repositories; + +import io.github.patternatlas.api.entities.user.role.Privilege; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; +import java.util.UUID; +import java.util.List; + +public interface PrivilegeRepository extends JpaRepository { + + public Optional findByName(String name); + + @Query(value = "SELECT * FROM privilege p WHERE p.name like '%ALL' OR p.name like '%CREATE'", nativeQuery = true) + public List findAllPlatformPrivileges(); + + @Query(value = "SELECT * FROM privilege p WHERE (p.name LIKE 'ISSUE%' OR p.name LIKE 'PATTERN_CANDIDATE%' OR p.name LIKE 'APPROVED_PATTERN%' OR p.name LIKE 'PATTERN_VIEW%') AND p.name NOT LIKE '%ALL' AND p.name NOT LIKE '%CREATE' AND p.name NOT LIKE '%\\_%-%'", nativeQuery = true) + public List findAllDefaultAuthorPrivileges(); + + @Query(value = "SELECT * FROM privilege p WHERE p.name like %:entityId", nativeQuery = true) + public List findAllFromEntity(@Param("entityId") UUID entityId); + + @Query(value = "SELECT * FROM privilege p WHERE p.name like CONCAT(:defaultAuthorPrivilege, '_________-____-____-____-____________')", nativeQuery = true) + public List findAllResourceSpecific(@Param("defaultAuthorPrivilege") String defaultAuthorPrivilege); + + @Modifying + @Query(value = "DELETE FROM privilege p WHERE p.name like %:entityId", nativeQuery = true) + public void deleteAllFromEntity(@Param("entityId") UUID entityId); + + @Query(value = "SELECT case when (count(priv) > 0) then true else false end from Privilege priv " + + "LEFT JOIN priv.roles r " + + "LEFT JOIN r.users u " + + "WHERE u.id = :userId " + + "AND priv.name = :privilegeName") + boolean existsPrivilegeForUser(@Param("privilegeName") String privilegeName, @Param("userId") UUID userId); +} diff --git a/src/main/java/io/github/patternatlas/api/repositories/RoleRepository.java b/src/main/java/io/github/patternatlas/api/repositories/RoleRepository.java new file mode 100644 index 0000000..e16d901 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/repositories/RoleRepository.java @@ -0,0 +1,47 @@ +package io.github.patternatlas.api.repositories; + +import io.github.patternatlas.api.entities.user.role.Role; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.UUID; +import java.util.List; + +public interface RoleRepository extends JpaRepository { + + boolean existsByName(String name); + Role findByName(String name); + + @Query(value = "SELECT * FROM role r WHERE r.name IN (:names)", nativeQuery = true) + public List findAllRolesByNames(@Param("names") List names); + + @Query(value = "SELECT * FROM role r WHERE r.name like %:entityId", nativeQuery = true) + public List findAllFromEntity(@Param("entityId") UUID entityId); + + @Query(value = "SELECT * from role r where r.name like %:entityId and r.name like :authorRole%", nativeQuery = true) + public List findAllFromEntityForAuthorRole(@Param("entityId") UUID entityId, @Param("authorRole") String authorRole); + + @Query(value = "SELECT * from role r where r.name like :authorRole%", nativeQuery = true) + public List findAllForAuthorRole(@Param("authorRole") String authorRole); + + @Modifying + @Query(value = "DELETE FROM role r WHERE r.name like %:entityId", nativeQuery = true) + public void deleteAllFromEntity(@Param("entityId") UUID entityId); + + @Query(value = "" + + "SELECT CASE\n" + + " WHEN count(r.name) > 0\n" + + " THEN true\n" + + " ELSE false\n" + + "END\n" + + "FROM role r\n" + + "JOIN role_privileges rp on r.id = rp.roles_id\n" + + "JOIN privilege p on rp.privileges_id = p.id\n" + + "WHERE r.id = :roleId\n" + + "AND p.name = :privilegeName", + nativeQuery = true) + public boolean existsPrivilegeForRole(@Param("privilegeName") String privilegeName, @Param("roleId") UUID roleId); +} diff --git a/src/main/java/io/github/patternatlas/api/repositories/UserRepository.java b/src/main/java/io/github/patternatlas/api/repositories/UserRepository.java index 26e0803..e8c43e0 100644 --- a/src/main/java/io/github/patternatlas/api/repositories/UserRepository.java +++ b/src/main/java/io/github/patternatlas/api/repositories/UserRepository.java @@ -2,6 +2,8 @@ import java.util.UUID; +import org.apache.catalina.User; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; @@ -9,5 +11,5 @@ @RepositoryRestResource(exported = false) public interface UserRepository extends JpaRepository { - + UserEntity findByEmail(String email); } diff --git a/src/main/java/io/github/patternatlas/api/rest/controller/AuthorController.java b/src/main/java/io/github/patternatlas/api/rest/controller/AuthorController.java new file mode 100644 index 0000000..c768672 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/controller/AuthorController.java @@ -0,0 +1,51 @@ +package io.github.patternatlas.api.rest.controller; + +import java.util.List; +import java.util.stream.Collectors; + +import io.github.patternatlas.api.entities.shared.AuthorConstant; +import io.github.patternatlas.api.rest.model.shared.AuthorModel; +import io.github.patternatlas.api.service.UserService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/authors", produces = "application/hal+json") +public class AuthorController { + + private UserService userService; + + public AuthorController( + UserService userService + ) { + this.userService = userService; + } + + /** + * GET Methods + */ + + // Only exposes username and ID for author management purposes - every user should be able to set others + // as authors, therefore it is not secured + @Operation(operationId = "getallUsersAsAuthors", responses = {@ApiResponse(responseCode = "200")}, description = "Retrieve all authors") + @GetMapping(value = "") + CollectionModel> getAll() { + List> authors = this.userService.getAllUsers() + .stream() + .map(user -> new EntityModel<>(new AuthorModel(user))) + .collect(Collectors.toList()); + return new CollectionModel<>(authors); + } + + @Operation(operationId = "getAllAuthorRoles", responses = {@ApiResponse(responseCode = "200")}, description = "Retrieve all author roles") + @GetMapping(value = "/roles") + String[] getAllRoles() { + return new String[]{AuthorConstant.HELPER, AuthorConstant.MAINTAINER, AuthorConstant.OWNER}; + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/controller/CandidateController.java b/src/main/java/io/github/patternatlas/api/rest/controller/CandidateController.java index e89e56a..9d2ea1b 100644 --- a/src/main/java/io/github/patternatlas/api/rest/controller/CandidateController.java +++ b/src/main/java/io/github/patternatlas/api/rest/controller/CandidateController.java @@ -1,16 +1,28 @@ package io.github.patternatlas.api.rest.controller; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.github.patternatlas.api.rest.model.candidate.CandidateModel; +import io.github.patternatlas.api.rest.model.candidate.CandidateModelRequest; +import io.github.patternatlas.api.rest.model.shared.AuthorModelRequest; +import io.github.patternatlas.api.rest.model.shared.CommentModel; +import io.github.patternatlas.api.rest.model.shared.EvidenceModel; +import io.github.patternatlas.api.rest.model.shared.RatingModelMultiRequest; +import io.github.patternatlas.api.rest.model.shared.RatingModelRequest; +import io.github.patternatlas.api.service.CandidateService; +import io.github.patternatlas.api.service.PatternLanguageService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -18,19 +30,14 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.github.patternatlas.api.rest.model.CandidateModel; -import io.github.patternatlas.api.entities.candidate.Candidate; -import io.github.patternatlas.api.entities.candidate.CandidateComment; -import io.github.patternatlas.api.service.CandidateService; -import io.github.patternatlas.api.service.PatternLanguageService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; +import java.security.Principal; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; @RestController @RequestMapping(value = "/candidates", produces = "application/hal+json") @@ -38,9 +45,9 @@ public class CandidateController { Logger logger = LoggerFactory.getLogger(CandidateController.class); - private final CandidateService candidateService; - private final PatternLanguageService patternLanguageService; - private final ObjectMapper objectMapper; + private CandidateService candidateService; + private PatternLanguageService patternLanguageService; + private ObjectMapper objectMapper; public CandidateController( CandidateService candidateService, @@ -55,29 +62,36 @@ public CandidateController( /** * GET Methods */ - @Operation(operationId = "getAllCandiates", responses = {@ApiResponse(responseCode = "200")}, description = "Retrieve all candidates") + @Operation(operationId = "getAllCandidates", responses = {@ApiResponse(responseCode = "200")}, description = "Retrieve all candidates") @GetMapping(value = "") - CollectionModel> all() { - List> candidates = this.candidateService.getAllCandidates() - .stream() - .map(candidate -> new EntityModel<>(CandidateModel.from(candidate))) -// getPatternViewLinks(patternView))) - .collect(Collectors.toList()); + CollectionModel> getAllCandidates(@RequestParam(value = "lid", required = false) UUID languageId) { + + List> candidates; + if (languageId == null) { + candidates = this.candidateService.getAllCandidates() + .stream() + .map(candidate -> new EntityModel<>(new CandidateModel(candidate))) + .collect(Collectors.toList()); + } else { + candidates = this.candidateService.getAllCandidatesByLanguageId(languageId) + .stream() + .map(candidate -> new EntityModel<>(new CandidateModel(candidate))) + .collect(Collectors.toList()); + } return new CollectionModel<>(candidates); } @Operation(operationId = "getCandidateById", responses = {@ApiResponse(responseCode = "200"), @ApiResponse(responseCode = "404", content = @Content)}, description = "Retrieve a single candidate by id") @GetMapping(value = "/{candidateId}") - @PreAuthorize(value = "#oauth2.hasScope('read')") - Candidate getCandidateById(@PathVariable UUID candidateId) { - return this.candidateService.getCandidateById(candidateId); + ResponseEntity> getCandidateById(@PathVariable UUID candidateId) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.getCandidateById(candidateId)))); } @Operation(operationId = "getCandidateByURI", responses = {@ApiResponse(responseCode = "200"), @ApiResponse(responseCode = "404", content = @Content)}, description = "Retrieve a single candidate by URI") - @GetMapping(value = "/?uri={candidateUri}") - Candidate getCandidateByUri(@PathVariable String candidateUri) { - return this.candidateService.getCandidateByURI(candidateUri); + @GetMapping(value = "/findByUri") + ResponseEntity> getCandidateByUri(@RequestParam("uri") String candidateUri) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.getCandidateByURI(candidateUri)))); } /** @@ -86,15 +100,22 @@ Candidate getCandidateByUri(@PathVariable String candidateUri) { @Operation(operationId = "createCandidate", responses = {@ApiResponse(responseCode = "201")}, description = "Create a candidate") @PostMapping(value = "") @ResponseStatus(HttpStatus.CREATED) - Candidate newCandidate(@RequestBody CandidateModel candidate) { - return this.candidateService.createCandidate(candidate); + ResponseEntity> newCandidate(@RequestBody CandidateModelRequest candidateModelRequest, Principal principal) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.createCandidate(candidateModelRequest, UUID.fromString(principal.getName()))))); } @Operation(operationId = "createCandidateComment", responses = {@ApiResponse(responseCode = "201")}, description = "Create a candidate comment") - @PostMapping(value = "/{candidateId}/comments/{userId}") + @PostMapping(value = "/{candidateId}/comments") + @ResponseStatus(HttpStatus.CREATED) + ResponseEntity> newCandidateComment(@PathVariable UUID candidateId, Principal principal, @RequestBody CommentModel commentModel) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.createComment(candidateId, UUID.fromString(principal.getName()), commentModel)))); + } + + @Operation(operationId = "createCandidateEvidence", responses = {@ApiResponse(responseCode = "201")}, description = "Create a candidate evidence") + @PostMapping(value = "/{candidateId}/evidences") @ResponseStatus(HttpStatus.CREATED) - Candidate newCandidateComment(@PathVariable UUID candidateId, @PathVariable UUID userId, @RequestBody CandidateComment candidateComment) { - return this.candidateService.createComment(candidateId, userId, candidateComment); + ResponseEntity> newIssueEvidence(@PathVariable UUID candidateId, Principal principal, @RequestBody EvidenceModel evidenceModel) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.createEvidence(candidateId, UUID.fromString(principal.getName()), evidenceModel)))); } /** @@ -102,21 +123,47 @@ Candidate newCandidateComment(@PathVariable UUID candidateId, @PathVariable UUID */ @Operation(operationId = "updateCandidate", responses = {@ApiResponse(responseCode = "200")}, description = "Update a candidate") @PutMapping(value = "/{candidateId}") - Candidate putCandidate(@PathVariable UUID candidateId, @RequestBody Candidate candidate) { - logger.info(candidate.toString()); - return this.candidateService.updateCandidate(candidate); + @ResponseStatus(HttpStatus.ACCEPTED) + ResponseEntity> putCandidate(@PathVariable UUID candidateId, Principal principal, @RequestBody CandidateModelRequest candidateModelRequest) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.updateCandidate(candidateId, UUID.fromString(principal.getName()), candidateModelRequest)))); } - @Operation(operationId = "updateCandidateRating", responses = {@ApiResponse(responseCode = "200")}, description = "Update rating of a candidate") - @PutMapping(value = "/{candidateId}/users/{userId}/rating/{rating}") - Candidate putCandidateRating(@PathVariable UUID candidateId, @PathVariable UUID userId, @PathVariable String rating) { - return this.candidateService.userRating(candidateId, userId, rating); + @Operation(operationId = "updateCandidateRating", responses = {@ApiResponse(responseCode = "200")}, description = "Update a candidate rating") + @PutMapping(value = "/{candidateId}/ratings") + ResponseEntity> putCandidateRating(@PathVariable UUID candidateId, Principal principal, @RequestBody RatingModelMultiRequest ratingModelMultiRequest) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.updateCandidateRating(candidateId, UUID.fromString(principal.getName()), ratingModelMultiRequest)))); } - @Operation(operationId = "updateCandidateCommentRating", responses = {@ApiResponse(responseCode = "200")}, description = "Update rating of a candidate comment") - @PutMapping(value = "/comments/{candidateCommentId}/users/{userId}/rating/{rating}") - Candidate putCandidateCommentRating(@PathVariable UUID candidateCommentId, @PathVariable UUID userId, @PathVariable String rating) { - return this.candidateService.commentUserRating(candidateCommentId, userId, rating); + @Operation(operationId = "updateCandidateAuthors", responses = {@ApiResponse(responseCode = "200")}, description = "Update a candidate authors") + @PutMapping(value = "{candidateId}/authors") + ResponseEntity> putCandidateAuthor(@PathVariable UUID candidateId, @RequestBody AuthorModelRequest authorModelRequest) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.saveCandidateAuthor(candidateId, authorModelRequest)))); + } + + @Operation(operationId = "updateCandidateComment", responses = {@ApiResponse(responseCode = "200")}, description = "Update a candidate comment") + @PutMapping(value = "/{candidateId}/comments/{candidateCommentId}") + @ResponseStatus(HttpStatus.ACCEPTED) + ResponseEntity> putCandidateComment(@PathVariable UUID candidateId, @PathVariable UUID candidateCommentId, Principal principal, @RequestBody CommentModel commentModel) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.updateComment(candidateId, candidateCommentId, UUID.fromString(principal.getName()), commentModel)))); + } + + @Operation(operationId = "updateCandidateCommentRating", responses = {@ApiResponse(responseCode = "200")}, description = "Update a candidate comment rating") + @PutMapping(value = "/{candidateId}/comments/{candidateCommentId}/ratings") + ResponseEntity> putCandidateCommentRating(@PathVariable UUID candidateId, @PathVariable UUID candidateCommentId, Principal principal, @RequestBody RatingModelRequest ratingModelRequest) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.updateCandidateCommentRating(candidateId, candidateCommentId, UUID.fromString(principal.getName()), ratingModelRequest)))); + } + + @Operation(operationId = "updateCandidateEvidence", responses = {@ApiResponse(responseCode = "200")}, description = "Update a candidate evidence") + @PutMapping(value = "/{candidateId}/evidences/{candidateEvidenceId}") + @ResponseStatus(HttpStatus.ACCEPTED) + ResponseEntity> putIssueEvidence(@PathVariable UUID candidateId, @PathVariable UUID candidateEvidenceId, Principal principal, @RequestBody EvidenceModel evidenceModel) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.updateEvidence(candidateId, candidateEvidenceId, UUID.fromString(principal.getName()), evidenceModel)))); + } + + @Operation(operationId = "updateCandidateEvidenceRatings", responses = {@ApiResponse(responseCode = "200")}, description = "Update a candidate evidence rating") + @PutMapping(value = "/{candidateId}/evidences/{candidateEvidenceId}/ratings") + ResponseEntity> putCandidateEvidenceRating(@PathVariable UUID candidateId, @PathVariable UUID candidateEvidenceId, Principal principal, @RequestBody RatingModelRequest ratingModelRequest) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.updateCandidateEvidenceRating(candidateId, candidateEvidenceId, UUID.fromString(principal.getName()), ratingModelRequest)))); } /** @@ -124,9 +171,29 @@ Candidate putCandidateCommentRating(@PathVariable UUID candidateCommentId, @Path */ @Operation(operationId = "deleteCandidateById", responses = {@ApiResponse(responseCode = "204")}, description = "Delete candidate by id") @DeleteMapping(value = "/{candidateId}") -// @PreAuthorize(value = "#oauth2.hasScope('de')") - ResponseEntity deleteCandidate(@PathVariable UUID candidateId) { - this.candidateService.deleteCandidate(candidateId); + @ResponseStatus(HttpStatus.OK) + ResponseEntity deleteCandidate(@PathVariable UUID candidateId, Principal principal) { + this.candidateService.deleteCandidate(candidateId, UUID.fromString(principal.getName())); return ResponseEntity.noContent().build(); } -} + + @Operation(operationId = "deleteCandidateAuthor", responses = {@ApiResponse(responseCode = "204")}, description = "Delete candidate author") + @DeleteMapping(value = "{candidateId}/authors/{userId}") + ResponseEntity> deleteCandidateAuthor(@PathVariable UUID candidateId, @PathVariable UUID userId) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.deleteCandidateAuthor(candidateId, userId)))); + } + + @Operation(operationId = "deleteCandidateComment", responses = {@ApiResponse(responseCode = "204")}, description = "Delete candidate comment") + @DeleteMapping(value = "/{candidateId}/comments/{candidateCommentId}") + @ResponseStatus(HttpStatus.OK) + ResponseEntity> deleteComment(@PathVariable UUID candidateId, @PathVariable UUID candidateCommentId, Principal principal) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.deleteComment(candidateId, candidateCommentId, UUID.fromString(principal.getName()))))); + } + + @Operation(operationId = "deleteCandidateEvidence", responses = {@ApiResponse(responseCode = "204")}, description = "Delete candidate evidence") + @DeleteMapping(value = "/{candidateId}/evidences/{candidateEvidenceId}") + @ResponseStatus(HttpStatus.OK) + ResponseEntity> deleteEvidence(@PathVariable UUID candidateId, @PathVariable UUID candidateEvidenceId, Principal principal) { + return ResponseEntity.ok(new EntityModel<>(new CandidateModel(this.candidateService.deleteEvidence(candidateId, candidateEvidenceId, UUID.fromString(principal.getName()))))); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/rest/controller/DesignModelController.java b/src/main/java/io/github/patternatlas/api/rest/controller/DesignModelController.java index fa2af05..00de902 100644 --- a/src/main/java/io/github/patternatlas/api/rest/controller/DesignModelController.java +++ b/src/main/java/io/github/patternatlas/api/rest/controller/DesignModelController.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; import org.apache.commons.text.CaseUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; @@ -53,6 +54,7 @@ import lombok.extern.apachecommons.CommonsLog; @RestController +@ConditionalOnExpression(value = "false") // TODO: set to true if the design models should be used again @CommonsLog @CrossOrigin(allowedHeaders = "*", origins = "*") @RequestMapping(value = "/design-models", produces = "application/hal+json") diff --git a/src/main/java/io/github/patternatlas/api/rest/controller/DiscussionController.java b/src/main/java/io/github/patternatlas/api/rest/controller/DiscussionController.java index cee9818..b74f341 100644 --- a/src/main/java/io/github/patternatlas/api/rest/controller/DiscussionController.java +++ b/src/main/java/io/github/patternatlas/api/rest/controller/DiscussionController.java @@ -4,6 +4,7 @@ import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; @@ -22,6 +23,7 @@ import io.github.patternatlas.api.service.DiscussionService; @RestController +@ConditionalOnExpression(value = "false") // TODO: check if discussions can be removed completely @CrossOrigin(allowedHeaders = "*", origins = "*") public class DiscussionController { diff --git a/src/main/java/io/github/patternatlas/api/rest/controller/ImageController.java b/src/main/java/io/github/patternatlas/api/rest/controller/ImageController.java index 4499e31..f44fb6f 100644 --- a/src/main/java/io/github/patternatlas/api/rest/controller/ImageController.java +++ b/src/main/java/io/github/patternatlas/api/rest/controller/ImageController.java @@ -3,6 +3,7 @@ import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; @@ -19,6 +20,7 @@ import io.github.patternatlas.api.service.ImageService; @RestController +@ConditionalOnExpression(value = "false") // TODO: check if the ImageController can be removed entirely @CrossOrigin(allowedHeaders = "*", origins = "*") public class ImageController { diff --git a/src/main/java/io/github/patternatlas/api/rest/controller/IssueController.java b/src/main/java/io/github/patternatlas/api/rest/controller/IssueController.java index 11fad87..5b7ffc9 100644 --- a/src/main/java/io/github/patternatlas/api/rest/controller/IssueController.java +++ b/src/main/java/io/github/patternatlas/api/rest/controller/IssueController.java @@ -1,13 +1,28 @@ package io.github.patternatlas.api.rest.controller; +import java.security.Principal; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; + +import io.github.patternatlas.api.rest.model.issue.IssueModel; +import io.github.patternatlas.api.rest.model.issue.IssueModelRequest; +import io.github.patternatlas.api.rest.model.shared.AuthorModelRequest; +import io.github.patternatlas.api.rest.model.shared.CommentModel; +import io.github.patternatlas.api.rest.model.shared.EvidenceModel; +import io.github.patternatlas.api.rest.model.shared.RatingModelRequest; +import io.github.patternatlas.api.service.IssueService; +import io.github.patternatlas.api.service.PatternLanguageService; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -15,27 +30,19 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.github.patternatlas.api.entities.issue.Issue; -import io.github.patternatlas.api.entities.issue.IssueComment; -import io.github.patternatlas.api.service.IssueService; -import io.github.patternatlas.api.service.PatternLanguageService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; - @RestController @RequestMapping(value = "/issues", produces = "application/hal+json") public class IssueController { Logger logger = LoggerFactory.getLogger(IssueController.class); - private final IssueService issueService; - private final PatternLanguageService patternLanguageService; - private final ObjectMapper objectMapper; + private IssueService issueService; + private PatternLanguageService patternLanguageService; + private ObjectMapper objectMapper; public IssueController( IssueService issueService, @@ -50,23 +57,29 @@ public IssueController( /** * GET Methods */ + + @Operation(operationId = "getIssueByURI", responses = {@ApiResponse(responseCode = "200")}, description = "Retrieve issue by URI") + @GetMapping(value = "/findByUri") + ResponseEntity> getIssueByUri(@RequestParam String uri) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.getIssueByURI(uri)))); + } + + @Operation(operationId = "getAllIssues", responses = {@ApiResponse(responseCode = "200")}, description = "Retrieve all issues") @GetMapping(value = "") - List all() { - return this.issueService.getAllIssues(); + CollectionModel> getAll() { + List> issues = this.issueService.getAllIssues() + .stream() + .map(issue -> new EntityModel<>(new IssueModel(issue))) + .sorted((i1, i2) -> Integer.compare(i2.getContent().getRating(), i1.getContent().getRating())) + .collect(Collectors.toList()); + return CollectionModel.of(issues); } @Operation(operationId = "getIssueById", responses = {@ApiResponse(responseCode = "200")}, description = "Retrieve issue by id") @GetMapping(value = "/{issueId}") - @PreAuthorize(value = "#oauth2.hasScope('read')") - Issue getIssueById(@PathVariable UUID issueId) { - return this.issueService.getIssueById(issueId); - } - - @Operation(operationId = "getIssueByURI", responses = {@ApiResponse(responseCode = "200")}, description = "Retrieve issue by URI") - @GetMapping(value = "/?uri={issueUri}") - Issue getIssueByUri(@PathVariable String issueUri) { - return this.issueService.getIssueByURI(issueUri); + ResponseEntity> getIssueById(@PathVariable UUID issueId) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.getIssueById(issueId)))); } /** @@ -74,18 +87,23 @@ Issue getIssueByUri(@PathVariable String issueUri) { */ @Operation(operationId = "createIssue", responses = {@ApiResponse(responseCode = "201")}, description = "Create an issue") @PostMapping(value = "") - @PreAuthorize(value = "#oauth2.hasScope('write')") @ResponseStatus(HttpStatus.CREATED) - Issue newIssue(@RequestBody Issue issue) { - return this.issueService.createIssue(issue); + ResponseEntity> newIssue(@RequestBody IssueModelRequest issueModelRequest, Principal principal) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.createIssue(issueModelRequest, UUID.fromString(principal.getName()))))); } @Operation(operationId = "createIssueComment", responses = {@ApiResponse(responseCode = "201")}, description = "Create an issue comment") - @PostMapping(value = "/{issueId}/comments/{userId}") -// @PreAuthorize(value = "#oauth2.hasScope('write')") + @PostMapping(value = "/{issueId}/comments") @ResponseStatus(HttpStatus.CREATED) - Issue newIssueComment(@PathVariable UUID issueId, @PathVariable UUID userId, @RequestBody IssueComment issueComment) { - return this.issueService.createComment(issueId, userId, issueComment); + ResponseEntity> newIssueComment(@PathVariable UUID issueId, Principal principal, @RequestBody CommentModel commentModel) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.createComment(issueId, UUID.fromString(principal.getName()), commentModel)))); + } + + @Operation(operationId = "createIssueEvidence", responses = {@ApiResponse(responseCode = "201")}, description = "Create an issue evidence") + @PostMapping(value = "/{issueId}/evidences") + @ResponseStatus(HttpStatus.CREATED) + ResponseEntity> newIssueEvidence(@PathVariable UUID issueId, Principal principal, @RequestBody EvidenceModel evidenceModel) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.createEvidence(issueId, UUID.fromString(principal.getName()), evidenceModel)))); } /** @@ -93,21 +111,44 @@ Issue newIssueComment(@PathVariable UUID issueId, @PathVariable UUID userId, @Re */ @Operation(operationId = "updateIssue", responses = {@ApiResponse(responseCode = "200")}, description = "Update an issue") @PutMapping(value = "/{issueId}") - Issue putIssue(@PathVariable UUID issueId, @RequestBody Issue issue) { - logger.info(issue.toString()); - return this.issueService.updateIssue(issue); + ResponseEntity> putIssue(@PathVariable UUID issueId, Principal principal, @RequestBody IssueModelRequest issueModelRequest) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.updateIssue(issueId, UUID.fromString(principal.getName()), issueModelRequest)))); + } + + @Operation(operationId = "updateIssueRatings", responses = {@ApiResponse(responseCode = "200")}, description = "Update an issue ratings") + @PutMapping(value = "/{issueId}/ratings") + ResponseEntity> putIssueRating(@PathVariable UUID issueId, Principal principal, @RequestBody RatingModelRequest ratingModelRequest) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.updateIssueRating(issueId, UUID.fromString(principal.getName()), ratingModelRequest)))); + } + + @Operation(operationId = "updateIssueAuthors", responses = {@ApiResponse(responseCode = "200")}, description = "Update an issue authors") + @PutMapping(value = "{issueId}/authors") + ResponseEntity> putIssueAuthor(@PathVariable UUID issueId, @RequestBody AuthorModelRequest authorModelRequest) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.saveIssueAuthor(issueId, authorModelRequest)))); + } + + @Operation(operationId = "updateIssue", responses = {@ApiResponse(responseCode = "200")}, description = "Update an issue comment") + @PutMapping(value = "/{issueId}/comments/{issueCommentId}") + ResponseEntity> putIssueComment(@PathVariable UUID issueId, @PathVariable UUID issueCommentId, Principal principal, @RequestBody CommentModel commentModel) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.updateComment(issueId, issueCommentId, UUID.fromString(principal.getName()), commentModel)))); + } + + @Operation(operationId = "updateIssue", responses = {@ApiResponse(responseCode = "200")}, description = "Update an issue comment rating") + @PutMapping(value = "/{issueId}/comments/{issueCommentId}/ratings") + ResponseEntity> putIssueCommentRating(@PathVariable UUID issueId, @PathVariable UUID issueCommentId, Principal principal, @RequestBody RatingModelRequest ratingModelRequest) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.updateIssueCommentRating(issueId, issueCommentId, UUID.fromString(principal.getName()), ratingModelRequest)))); } - @Operation(operationId = "updateIssueRating", responses = {@ApiResponse(responseCode = "200")}, description = "Update an issue rating") - @PutMapping(value = "/{issueId}/users/{userId}/rating/{rating}") - Issue putIssueRating(@PathVariable UUID issueId, @PathVariable UUID userId, @PathVariable String rating) { - return this.issueService.userRating(issueId, userId, rating); + @Operation(operationId = "updateIssue", responses = {@ApiResponse(responseCode = "200")}, description = "Update an issue evidence") + @PutMapping(value = "/{issueId}/evidences/{issueEvidenceId}") + ResponseEntity> putIssueEvidence(@PathVariable UUID issueId, @PathVariable UUID issueEvidenceId, Principal principal, @RequestBody EvidenceModel evidenceModel) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.updateEvidence(issueId, issueEvidenceId, UUID.fromString(principal.getName()), evidenceModel)))); } - @Operation(operationId = "updateIssueCommentRating", responses = {@ApiResponse(responseCode = "200")}, description = "Update an issue comment rating") - @PutMapping(value = "/comments/{issueCommentId}/users/{userId}/rating/{rating}") - Issue putIssueCommentRating(@PathVariable UUID issueCommentId, @PathVariable UUID userId, @PathVariable String rating) { - return this.issueService.commentUserRating(issueCommentId, userId, rating); + @Operation(operationId = "updateIssueEvidenceRating", responses = {@ApiResponse(responseCode = "200")}, description = "Update an issue evidence rating") + @PutMapping(value = "/{issueId}/evidences/{issueEvidenceId}/ratings") + ResponseEntity> putIssueEvidenceRating(@PathVariable UUID issueId, @PathVariable UUID issueEvidenceId, Principal principal, @RequestBody RatingModelRequest ratingModelRequest) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.updateIssueEvidenceRating(issueId, issueEvidenceId, UUID.fromString(principal.getName()), ratingModelRequest)))); } /** @@ -115,9 +156,26 @@ Issue putIssueCommentRating(@PathVariable UUID issueCommentId, @PathVariable UUI */ @Operation(operationId = "deleteIssue", responses = {@ApiResponse(responseCode = "200")}, description = "Delete an issue") @DeleteMapping(value = "/{issueId}") -// @PreAuthorize(value = "#oauth2.hasScope('de')") ResponseEntity deleteIssue(@PathVariable UUID issueId) { this.issueService.deleteIssue(issueId); return ResponseEntity.noContent().build(); } -} + + @Operation(operationId = "deleteIssueAuthor", responses = {@ApiResponse(responseCode = "200")}, description = "Delete an issue") + @DeleteMapping(value = "{issueId}/authors/{userId}") + ResponseEntity> deleteIssueAuthor(@PathVariable UUID issueId, @PathVariable UUID userId) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.deleteIssueAuthor(issueId, userId)))); + } + + @Operation(operationId = "deleteIssueComment", responses = {@ApiResponse(responseCode = "200")}, description = "Delete an issue comment") + @DeleteMapping(value = "/{issueId}/comments/{issueCommentId}") + ResponseEntity> deleteComment(@PathVariable UUID issueId, @PathVariable UUID issueCommentId, Principal principal) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.deleteComment(issueId, issueCommentId, UUID.fromString(principal.getName()))))); + } + + @Operation(operationId = "deleteIssueEvidence", responses = {@ApiResponse(responseCode = "200")}, description = "Delete an issue evidence") + @DeleteMapping(value = "/{issueId}/evidences/{issueEvidenceId}") + ResponseEntity> deleteEvidence(@PathVariable UUID issueId, @PathVariable UUID issueEvidenceId, Principal principal) { + return ResponseEntity.ok(new EntityModel<>(new IssueModel(this.issueService.deleteEvidence(issueId, issueEvidenceId, UUID.fromString(principal.getName()))))); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/rest/controller/PatternController.java b/src/main/java/io/github/patternatlas/api/rest/controller/PatternController.java index d9c9ef0..be01399 100644 --- a/src/main/java/io/github/patternatlas/api/rest/controller/PatternController.java +++ b/src/main/java/io/github/patternatlas/api/rest/controller/PatternController.java @@ -358,7 +358,7 @@ ResponseEntity removePatternFromView(@PathVariable UUID patternViewId, @PathV return ResponseEntity.noContent().build(); } - @Operation(operationId = "addPatternToPatternLanguage", responses = {@ApiResponse(responseCode = "201")}, description = "Delete pattern from pattern view") + @Operation(operationId = "addPatternToPatternLanguage", responses = {@ApiResponse(responseCode = "201")}, description = "Add pattern to pattern language") @PostMapping(value = "/patternLanguages/{patternLanguageId}/patterns") @CrossOrigin(exposedHeaders = "Location") @ResponseStatus(HttpStatus.CREATED) diff --git a/src/main/java/io/github/patternatlas/api/rest/controller/PatternLanguageController.java b/src/main/java/io/github/patternatlas/api/rest/controller/PatternLanguageController.java index bb68847..e7ddc23 100644 --- a/src/main/java/io/github/patternatlas/api/rest/controller/PatternLanguageController.java +++ b/src/main/java/io/github/patternatlas/api/rest/controller/PatternLanguageController.java @@ -39,7 +39,11 @@ import io.github.patternatlas.api.entities.PatternLanguage; import io.github.patternatlas.api.entities.PatternSchema; import io.github.patternatlas.api.rest.model.PatternLanguageModel; +import io.github.patternatlas.api.rest.model.shared.PatternSchemaModel; +import io.github.patternatlas.api.rest.model.PatternLanguageGraphModel; +import io.github.patternatlas.api.rest.model.shared.PatternLanguageSchemaModel; import io.github.patternatlas.api.service.PatternLanguageService; + import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -147,6 +151,16 @@ ResponseEntity deletePatternLanguage(@PathVariable UUID patternLanguageId) return ResponseEntity.noContent().build(); } + @GetMapping(value = "/patternSchemas") + CollectionModel> getAllPatternLanguagesWithSchema() { + List> patternLanguages = this.patternLanguageService.getPatternLanguages() + .stream() + .map(patternLanguage -> new EntityModel<>(new PatternLanguageSchemaModel(patternLanguage))) + .collect(Collectors.toList()); + + return new CollectionModel<>(patternLanguages); + } + @Operation(operationId = "getPatternSchema", responses = {@ApiResponse(responseCode = "200")}, description = "Get pattern schema by pattern language id") @GetMapping(value = "/{patternLanguageId}/patternSchema") EntityModel getPatternSchema(@PathVariable UUID patternLanguageId) { diff --git a/src/main/java/io/github/patternatlas/api/rest/controller/UserController.java b/src/main/java/io/github/patternatlas/api/rest/controller/UserController.java index 739d8c6..98f24ab 100644 --- a/src/main/java/io/github/patternatlas/api/rest/controller/UserController.java +++ b/src/main/java/io/github/patternatlas/api/rest/controller/UserController.java @@ -1,14 +1,33 @@ package io.github.patternatlas.api.rest.controller; -import java.util.ArrayList; -import java.util.Arrays; +import java.security.Principal; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; +import io.github.patternatlas.api.entities.user.UserEntity; +import io.github.patternatlas.api.entities.user.role.Privilege; +import io.github.patternatlas.api.entities.user.role.Role; +import io.github.patternatlas.api.rest.model.user.PrivilegeModel; +import io.github.patternatlas.api.rest.model.user.RoleModel; +import io.github.patternatlas.api.rest.model.user.RoleModelRequest; +import io.github.patternatlas.api.rest.model.user.UserModel; +import io.github.patternatlas.api.rest.model.user.UserModelRequest; +import io.github.patternatlas.api.service.UserService; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; import org.springframework.http.HttpStatus; -import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -17,18 +36,11 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.entities.user.UserRole; -import io.github.patternatlas.api.service.UserService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; - @RestController @RequestMapping(value = "/users", produces = "application/hal+json") public class UserController { @@ -37,7 +49,7 @@ public class UserController { private final UserService userService; private final PasswordEncoder passwordEncoder; - private final ObjectMapper objectMapper; + private ObjectMapper objectMapper; public UserController( UserService userService, @@ -49,19 +61,118 @@ public UserController( this.objectMapper = objectMapper; } + /** * GET Methods */ + + // Exposes whole activity of each user (including created issues, candidates...). This should only be + // allowed for USER_READ_ALL, or USER_READ if it is for user themselves @Operation(operationId = "getAllUsers", responses = {@ApiResponse(responseCode = "200")}, description = "Retrieve all users") @GetMapping(value = "") - @PreAuthorize(value = "hasAuthority('ADMIN')") - List all() { - return this.userService.getAllUsers(); + @PostFilter(value = "hasGlobalPermission(@PC.USER_READ_ALL) or " + + "(hasGlobalPermission(@PC.USER_READ) and filterObject.getContent().id.equals(loggedInUUID()))") + CollectionModel> getAll() { + List> users = this.userService.getAllUsers() + .stream() + .map(user -> new EntityModel<>(new UserModel(user))) + .collect(Collectors.toList()); + // and ) + return CollectionModel.of(users); } @GetMapping(value = "/{userId}") - UserEntity getUserById(@PathVariable UUID userId) { - return this.userService.getUserById(userId); + ResponseEntity> getUserById(@PathVariable UUID userId) { + return ResponseEntity.ok(EntityModel.of(new UserModel(this.userService.getUserById(userId)))); + } + + /** + * Extended userinfo endpoint replaces endpoint in auth server. + * @param principal + * @return + */ + @RequestMapping(method = RequestMethod.GET, value = "/userinfo") + @ResponseBody + public Map user(Principal principal) { + if (principal != null) { + UUID id = UUID.fromString(principal.getName()); + UserEntity user = this.userService.getUserById(id); + Map model = new HashMap(); + model.put("name", user.getName()); + model.put("id", user.getId()); + model.put("role", user.getRoles().stream() + .map(Role::getName) + .collect(Collectors.toList())); + model.put("privileges", user.getRoles().stream() + .flatMap(role -> role.getPrivileges().stream()) + .map(Privilege::getName) + .collect(Collectors.toList())); + return model; + } + return null; + } + + @GetMapping(value = "/roles") + CollectionModel> getAllRoles() { + List> roles = this.userService.getAllRoles() + .stream() + .map(role -> EntityModel.of(new RoleModel(role))) + .collect(Collectors.toList()); + return CollectionModel.of(roles); + } + + @GetMapping(value = "/roles/platform") + CollectionModel> getAllPlatformRoles() { + List> roles = this.userService.getAllPlatformRoles() + .stream() + .map(role -> EntityModel.of(new RoleModel(role))) + .collect(Collectors.toList()); + return CollectionModel.of(roles); + } + + @GetMapping(value = "/roles/authors") + CollectionModel> getAllAuthorRoles() { + List> roles = this.userService.getAllAuthorRoles() + .stream() + .map(role -> EntityModel.of(new RoleModel(role))) + .collect(Collectors.toList()); + return CollectionModel.of(roles); + } + + @GetMapping(value = "/roles/{entityId}") + CollectionModel> getAllRolesFromEntity(@PathVariable UUID entityId) { + List> roles = this.userService.getAllRolesFromEntity(entityId) + .stream() + .map(role -> EntityModel.of(new RoleModel(role))) + .collect(Collectors.toList()); + return CollectionModel.of(roles); + } + + @GetMapping(value = "/roles/privileges") + CollectionModel> getAllPlatformPrivileges() { + List> privileges = this.userService.getAllPlatformPrivileges() + .stream() + .map(privilege -> EntityModel.of(new PrivilegeModel(privilege))) + .collect(Collectors.toList()); + return CollectionModel.of(privileges); + } + + @GetMapping(value = "/roles/default_author_privileges") + CollectionModel> getAllDefaultAuthorPrivileges() { + List> privileges = this.userService.getAllDefaultAuthorPrivileges() + .stream() + .map(privilege -> EntityModel.of(new PrivilegeModel(privilege))) + .collect(Collectors.toList()); + return CollectionModel.of(privileges); + } + + @GetMapping(value = "/roles/privileges/{entityId}") + CollectionModel> getAllPrivilegesFromEntity(@PathVariable UUID entityId) { + List> privileges = this.userService.getAllPrivilegesFromEntity(entityId) + .stream() + .map(privilege -> EntityModel.of(new PrivilegeModel(privilege))) + .collect(Collectors.toList()); + return CollectionModel.of(privileges); } /** @@ -70,18 +181,8 @@ UserEntity getUserById(@PathVariable UUID userId) { @Operation(operationId = "createUser", responses = {@ApiResponse(responseCode = "200")}, description = "Create a user") @PostMapping(value = "") @ResponseStatus(HttpStatus.CREATED) - public UserEntity newUser(@RequestBody UserEntity user) { - user.setPassword(passwordEncoder.encode(user.getPassword())); - return this.userService.createUser(user); - } - - public void defaultUsers() { - List role = new ArrayList<>(Arrays.asList(UserRole.MEMBER)); - UserEntity userMember = new UserEntity("Member User", "member@mail", passwordEncoder.encode("pass"), role); - this.userService.createUser(userMember); - role.add(UserRole.ADMIN); - UserEntity userAdmin = new UserEntity("Admin User", "admin@mail", passwordEncoder.encode("pass"), role); - this.userService.createUser(userAdmin); + ResponseEntity> newUser(@RequestBody UserModelRequest userModelRequest) { + return ResponseEntity.ok(EntityModel.of(new UserModel(this.userService.createUser(userModelRequest)))); } /** @@ -90,8 +191,39 @@ public void defaultUsers() { @Operation(operationId = "updateUser", responses = {@ApiResponse(responseCode = "200"), @ApiResponse(responseCode = "404", content = @Content)}, description = "Update a user") @PutMapping(value = "/{userId}") @ResponseStatus(HttpStatus.ACCEPTED) - UserEntity updateUser(@PathVariable UUID userId, @RequestBody UserEntity user) { - return this.userService.updateUser(user); + ResponseEntity> updateUser(@PathVariable UUID userId, @RequestBody UserModelRequest userModelRequest) { + return ResponseEntity.ok(EntityModel.of(new UserModel(this.userService.updateUser(userId, userModelRequest)))); + } + + @Operation(operationId = "updatePlatformRole", responses = {@ApiResponse(responseCode = "200"), @ApiResponse(responseCode = "404", content = @Content)}, description = "Update platform wide roles for user") + @PutMapping(value = "/{userId}/role") + @ResponseStatus(HttpStatus.ACCEPTED) + ResponseEntity> updatePlatformRole(@PathVariable UUID userId, @RequestBody UserModelRequest userModelRequest) { + return ResponseEntity.ok(EntityModel.of(new UserModel(this.userService.updatePlatformRole(userId, userModelRequest)))); + } + + @PutMapping(value = "/roles/{roleId}/privileges/{privilegeId}") + @ResponseStatus(HttpStatus.ACCEPTED) + ResponseEntity> updateUserRole(@PathVariable UUID roleId, @PathVariable UUID privilegeId, @RequestBody RoleModelRequest roleModelRequest) { + return ResponseEntity.ok(EntityModel.of(new RoleModel(this.userService.updateRole(roleId, privilegeId, roleModelRequest)))); + } + + /** + * This endpoint updates all resource specific roles that are related to the specified author role (HELPER, MAINTAINER or OWNER) according to the value of the specified default author privilege. + * e.g. the role is 'HELPER', the default author privilege is 'PATTERN_CANDIDATE_EDIT' and the checkboxValue of roleModelRequest is false. This + * means the privilege 'PATTERN_CANDIDATE_EDIT' should be removed from all 'HELPER' roles from all resources. If a resources has + * the ID 123, privilege 'PATTERN_CANDIDATE_EDIT_123' will be removed from the role 'HELPER_PATTERN_CANDIDATE_123' + * + * @param authorRoleId + * @param defaultAuthorPrivilegeId + * @param roleModelRequest + * @return + */ + @Operation(description = "Update all resource specific roles according to the value of the specified default author privilege") + @PutMapping(value = "/roles/{authorRoleId}/privileges/{defaultAuthorPrivilegeId}/all_resource_specific") + @ResponseStatus(HttpStatus.ACCEPTED) + void updateAllResourceSpecificAuthorRoles(@PathVariable UUID authorRoleId, @PathVariable UUID defaultAuthorPrivilegeId, @RequestBody RoleModelRequest roleModelRequest) { + this.userService.updateAllResourceSpecificRoles(authorRoleId, defaultAuthorPrivilegeId, roleModelRequest); } /** @@ -99,7 +231,8 @@ UserEntity updateUser(@PathVariable UUID userId, @RequestBody UserEntity user) { */ @Operation(operationId = "deleteUser", responses = {@ApiResponse(responseCode = "200"), @ApiResponse(responseCode = "404", content = @Content)}, description = "Delete a user") @DeleteMapping(value = "/{userId}") - void deleteUser(@PathVariable UUID userId) { + ResponseEntity deleteUser(@PathVariable UUID userId) { this.userService.deleteUser(userId); + return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/io/github/patternatlas/api/rest/exception/RestResponseExceptionHandler.java b/src/main/java/io/github/patternatlas/api/rest/exception/RestResponseExceptionHandler.java index eda1818..74a9d1e 100644 --- a/src/main/java/io/github/patternatlas/api/rest/exception/RestResponseExceptionHandler.java +++ b/src/main/java/io/github/patternatlas/api/rest/exception/RestResponseExceptionHandler.java @@ -1,5 +1,14 @@ package io.github.patternatlas.api.rest.exception; +import io.github.patternatlas.api.exception.DirectedEdgeNotFoundException; +import io.github.patternatlas.api.exception.NullPatternSchemaException; +import io.github.patternatlas.api.exception.PatternLanguageNotFoundException; +import io.github.patternatlas.api.exception.PatternNotFoundException; +import io.github.patternatlas.api.exception.PatternSchemaNotFoundException; +import io.github.patternatlas.api.exception.PatternViewNotFoundException; +import io.github.patternatlas.api.exception.UndirectedEdgeNotFoundException; +import io.github.patternatlas.api.rest.model.ErrorMessageDTO; + import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -9,33 +18,35 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import io.github.patternatlas.api.exception.NullPatternSchemaException; -import io.github.patternatlas.api.rest.model.ErrorMessageDTO; - @ControllerAdvice public class RestResponseExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(value = { - ResourceNotFoundException.class + ResourceNotFoundException.class, + PatternLanguageNotFoundException.class, + PatternNotFoundException.class, + PatternSchemaNotFoundException.class, + PatternViewNotFoundException.class, + DirectedEdgeNotFoundException.class, + UndirectedEdgeNotFoundException.class }) - protected ResponseEntity handleEntityNotFoundExceptions(Exception ex, WebRequest request) { - ErrorMessageDTO errorMessage = new ErrorMessageDTO(ex.getMessage(), HttpStatus.NOT_FOUND); - return handleExceptionInternal(ex, errorMessage, new HttpHeaders(), HttpStatus.NOT_FOUND, request); + protected ResponseEntity handleEntityNotFoundExceptions(RuntimeException ex, WebRequest request) { + return handleExceptionInternal(ex, ex.getMessage(), new HttpHeaders(), HttpStatus.NOT_FOUND, request); } @ExceptionHandler(value = { NullPatternSchemaException.class }) - protected ResponseEntity handleNullPatternSchemaException(Exception ex, WebRequest request) { - ErrorMessageDTO errorMessage = new ErrorMessageDTO(ex.getMessage(), HttpStatus.BAD_REQUEST); - return handleExceptionInternal(ex, errorMessage, new HttpHeaders(), HttpStatus.BAD_REQUEST, request); + protected ResponseEntity handleNullPatternSchemaException(RuntimeException ex, WebRequest request) { + return handleExceptionInternal(ex, ex.getMessage(), new HttpHeaders(), HttpStatus.BAD_REQUEST, request); } @ExceptionHandler(value = { Exception.class }) protected ResponseEntity handleStorageExceptions(Exception ex, WebRequest request) { + ex.printStackTrace(); ErrorMessageDTO errorMessage = new ErrorMessageDTO(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); return handleExceptionInternal(ex, errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request); } diff --git a/src/main/java/io/github/patternatlas/api/rest/model/CandidateModel.java b/src/main/java/io/github/patternatlas/api/rest/model/CandidateModel.java deleted file mode 100644 index e693354..0000000 --- a/src/main/java/io/github/patternatlas/api/rest/model/CandidateModel.java +++ /dev/null @@ -1,61 +0,0 @@ -package io.github.patternatlas.api.rest.model; - -import java.util.List; -import java.util.UUID; - -import io.github.patternatlas.api.entities.PatternLanguage; -import io.github.patternatlas.api.entities.candidate.Candidate; -import io.github.patternatlas.api.entities.candidate.CandidateComment; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@Data -@EqualsAndHashCode(callSuper = false) -public class CandidateModel { - - private UUID id; - - private String uri; - - private String name; - - private String iconUrl; - - private UUID patternLanguageId; - - private String patternLanguageName; - - private String content; - - private String version; - - private int rating; - - private List comments; - - private CandidateModel(Candidate candidate) { - PatternLanguage patternLanguage = candidate.getPatternLanguage(); - this.id = candidate.getId(); - this.uri = candidate.getUri(); - this.name = candidate.getName(); - this.iconUrl = candidate.getIconUrl(); - this.content = candidate.getContent(); - this.version = candidate.getVersion(); - this.rating = candidate.getRating(); - this.comments = candidate.getComments(); - - if (patternLanguage != null) { - this.patternLanguageId = patternLanguage.getId(); - this.patternLanguageName = patternLanguage.getName(); - } else { - this.patternLanguageId = null; - this.patternLanguageName = "NONE"; - } - } - - public static CandidateModel from(Candidate candidate) { - return new CandidateModel(candidate); - } -} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/IssueModel.java b/src/main/java/io/github/patternatlas/api/rest/model/IssueModel.java deleted file mode 100644 index a291202..0000000 --- a/src/main/java/io/github/patternatlas/api/rest/model/IssueModel.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.patternatlas.api.rest.model; - -import java.util.UUID; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@Data -@EqualsAndHashCode(callSuper = false) -public class IssueModel { - - private UUID id; - - private UUID pattern_language_id; - - private UUID[] author_group; - - private UUID[] user_voted; - - private Integer votes; - - private String name; - - private String description; - - private String version; - - private Boolean active; -} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/PatternLanguageGraphModel.java b/src/main/java/io/github/patternatlas/api/rest/model/PatternLanguageGraphModel.java new file mode 100644 index 0000000..5411a23 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/PatternLanguageGraphModel.java @@ -0,0 +1,10 @@ +package io.github.patternatlas.api.rest.model; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +public class PatternLanguageGraphModel { + private Object graph; +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/candidate/CandidateModel.java b/src/main/java/io/github/patternatlas/api/rest/model/candidate/CandidateModel.java new file mode 100644 index 0000000..c9637b9 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/candidate/CandidateModel.java @@ -0,0 +1,88 @@ +package io.github.patternatlas.api.rest.model.candidate; + +import io.github.patternatlas.api.entities.PatternLanguage; +import io.github.patternatlas.api.entities.candidate.Candidate; +import io.github.patternatlas.api.rest.model.shared.AuthorModel; +import io.github.patternatlas.api.rest.model.shared.CommentModel; +import io.github.patternatlas.api.rest.model.shared.EvidenceModel; +import io.github.patternatlas.api.rest.model.shared.RatingModel; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class CandidateModel { + + private UUID id; + private String uri; + private String name; + private String iconUrl; + private UUID patternLanguageId; + private String patternLanguageName; + private Object content; + private String version; + private int rating; + // RESPONSE + private double ratingReadability = 0; + private double ratingUnderstandability = 0; + private double ratingAppropriateness = 0; + private Collection readability = new ArrayList<>(); + private Collection understandability = new ArrayList<>(); + private Collection appropriateness = new ArrayList<>(); + private List authors = new ArrayList<>(); + private List comments = new ArrayList<>(); + private List evidences = new ArrayList<>(); + + + public CandidateModel(Candidate candidate) { + PatternLanguage patternLanguage = candidate.getPatternLanguage(); + this.id = candidate.getId(); + this.uri = candidate.getUri(); + this.name = candidate.getName(); + this.iconUrl = candidate.getIconUrl(); + this.content = candidate.getContent(); + this.version = candidate.getVersion(); + // RESPONSE + this.readability = candidate.getUserRating().stream().map(candidateRating -> { + this.ratingReadability += candidateRating.getReadability(); + return new RatingModel(candidateRating, candidateRating.getReadability()); + }).collect(Collectors.toList()); + this.understandability = candidate.getUserRating().stream().map(candidateRating -> { + this.ratingUnderstandability += candidateRating.getUnderstandability(); + return new RatingModel(candidateRating, candidateRating.getUnderstandability()); + }).collect(Collectors.toList()); + this.appropriateness = candidate.getUserRating().stream().map(candidateRating -> { + this.ratingAppropriateness += candidateRating.getAppropriateness(); + return new RatingModel(candidateRating, candidateRating.getAppropriateness()); + }).collect(Collectors.toList()); + + this.ratingReadability = Math.round((this.ratingReadability / this.readability.size()) * 100.0) / 100.0; + this.ratingUnderstandability = Math.round((this.ratingUnderstandability / this.understandability.size()) * 100.0) / 100.0; + this.ratingAppropriateness = Math.round((this.ratingAppropriateness / this.appropriateness.size()) * 100.0) / 100.0; + + this.authors = candidate.getAuthors().stream().map(author -> new AuthorModel(author.getUser(), author.getRole())).collect(Collectors.toList()); + this.comments = candidate.getComments().stream().map(issueComment -> CommentModel.from(issueComment)) + .sorted((o1, o2) -> Integer.compare(o2.getRating(), o1.getRating())) + .collect(Collectors.toList()); + this.evidences = candidate.getEvidences().stream().map(candidateEvidence -> EvidenceModel.from(candidateEvidence)) + .sorted((o1, o2) -> Integer.compare(o2.getRating(), o1.getRating())) + .collect(Collectors.toList()); + + if (patternLanguage != null) { + this.patternLanguageId = patternLanguage.getId(); + this.patternLanguageName = patternLanguage.getName(); + } else { + this.patternLanguageId = null; + this.patternLanguageName = "NONE"; + } + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/candidate/CandidateModelRequest.java b/src/main/java/io/github/patternatlas/api/rest/model/candidate/CandidateModelRequest.java new file mode 100644 index 0000000..e11cdc0 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/candidate/CandidateModelRequest.java @@ -0,0 +1,22 @@ +package io.github.patternatlas.api.rest.model.candidate; + +import io.github.patternatlas.api.entities.candidate.Candidate; +import io.github.patternatlas.api.rest.model.shared.AuthorModel; +import io.github.patternatlas.api.rest.model.user.UserModel; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.UUID; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class CandidateModelRequest extends CandidateModel { + + private UUID issueId; + private Integer updateRating; + private List updateAuthors; +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/issue/IssueModel.java b/src/main/java/io/github/patternatlas/api/rest/model/issue/IssueModel.java new file mode 100644 index 0000000..07feffa --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/issue/IssueModel.java @@ -0,0 +1,64 @@ +package io.github.patternatlas.api.rest.model.issue; + +import io.github.patternatlas.api.entities.issue.Issue; +import io.github.patternatlas.api.entities.issue.IssueRating; +import io.github.patternatlas.api.rest.model.shared.AuthorModel; +import io.github.patternatlas.api.rest.model.shared.CommentModel; +import io.github.patternatlas.api.rest.model.shared.EvidenceModel; +import io.github.patternatlas.api.rest.model.shared.RatingModel; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class IssueModel { + + private UUID id; + private String uri; + private String name; + private String description; + private String version; + // RESPONSE + private int rating; + private Collection upVotes = new ArrayList<>(); + private Collection downVotes = new ArrayList<>(); + private List authors = new ArrayList<>(); + private List comments = new ArrayList<>(); + private List evidences = new ArrayList<>(); + + public IssueModel(Issue issue) { + this.id = issue.getId(); + this.uri = issue.getUri(); + this.name = issue.getName(); + this.description = issue.getDescription(); + this.version = issue.getVersion(); + //Response + this.rating = 0; + for (IssueRating issueRating: issue.getUserRating()) { + if (issueRating.getRating() == 1) { + this.upVotes.add(issueRating.getUser().getId()); + this.rating = this.rating + 1; + } + if (issueRating.getRating() == -1) { + this.downVotes.add(issueRating.getUser().getId()); + this.rating = this.rating - 1; + } + } + this.authors = issue.getAuthors().stream().map(issueAuthor -> new AuthorModel(issueAuthor.getUser(), issueAuthor.getRole())).collect(Collectors.toList()); + this.comments = issue.getComments().stream().map(issueComment -> CommentModel.from(issueComment)) + .sorted((o1, o2) -> Integer.compare(o2.getRating(), o1.getRating())) + .collect(Collectors.toList()); + this.evidences = issue.getEvidences().stream().map(issueEvidence -> EvidenceModel.from(issueEvidence)) + .sorted((o1, o2) -> Integer.compare(o2.getRating(), o1.getRating())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/issue/IssueModelRequest.java b/src/main/java/io/github/patternatlas/api/rest/model/issue/IssueModelRequest.java new file mode 100644 index 0000000..5719170 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/issue/IssueModelRequest.java @@ -0,0 +1,18 @@ +package io.github.patternatlas.api.rest.model.issue; + +import io.github.patternatlas.api.rest.model.shared.AuthorModel; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.List; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class IssueModelRequest extends IssueModel { + + private Integer updateRating; + private List updateAuthors; +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/AuthorModel.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/AuthorModel.java new file mode 100644 index 0000000..f0a1f6d --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/AuthorModel.java @@ -0,0 +1,43 @@ +package io.github.patternatlas.api.rest.model.shared; + +import io.github.patternatlas.api.entities.candidate.author.CandidateAuthor; +import io.github.patternatlas.api.entities.issue.author.IssueAuthor; +import io.github.patternatlas.api.entities.user.UserEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class AuthorModel { + + private UUID userId; + private String name; + private String authorRole; + + public AuthorModel(IssueAuthor issueAuthor) { + this.userId = issueAuthor.getUser().getId(); + this.name = issueAuthor.getUser().getName(); + this.authorRole = issueAuthor.getRole(); + } + + public AuthorModel(CandidateAuthor candidateAuthor) { + this.userId = candidateAuthor.getUser().getId(); + this.name = candidateAuthor.getUser().getName(); + this.authorRole = candidateAuthor.getRole(); + } + + public AuthorModel(UserEntity user) { + this.userId = user.getId(); + this.name = user.getName(); + } + + public AuthorModel(UserEntity user, String role) { + this(user); + this.authorRole = role; + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/AuthorModelRequest.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/AuthorModelRequest.java new file mode 100644 index 0000000..1283cc3 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/AuthorModelRequest.java @@ -0,0 +1,13 @@ +package io.github.patternatlas.api.rest.model.shared; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.List; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class AuthorModelRequest extends AuthorModel { +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/CommentModel.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/CommentModel.java new file mode 100644 index 0000000..658683a --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/CommentModel.java @@ -0,0 +1,83 @@ +package io.github.patternatlas.api.rest.model.shared; + +import io.github.patternatlas.api.entities.candidate.comment.CandidateComment; +import io.github.patternatlas.api.entities.candidate.comment.CandidateCommentRating; +import io.github.patternatlas.api.entities.issue.IssueRating; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; +import io.github.patternatlas.api.entities.issue.comment.IssueCommentRating; +import io.github.patternatlas.api.entities.shared.Comment; +import io.github.patternatlas.api.entities.user.UserEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class CommentModel { + + private UUID id; + private UUID userId; + private String userName; + private String text; + private int rating; + private Collection upVotes = new ArrayList<>(); + private Collection downVotes = new ArrayList<>(); + + private void initialize(Comment comment) { + this.id = comment.getId(); + this.userId = comment.getUser().getId(); + this.userName = comment.getUser().getName(); + this.text = comment.getText(); + this.rating = 0; + } + + private void updateRatingInformation(UserEntity user, int rating) { + if (rating == 1) { + this.upVotes.add(user.getId()); + this.rating = this.rating + 1; + } + if (rating == -1) { + this.downVotes.add(user.getId()); + this.rating = this.rating - 1; + } + } + + /** + * For Issue Comments + */ + public CommentModel(IssueComment issueComment) { + initialize(issueComment); + for (IssueCommentRating issueRating : issueComment.getUserRating()) { + updateRatingInformation(issueRating.getUser(), issueRating.getRating()); + } + } + + public static CommentModel from(IssueComment issueComment) { + return new CommentModel(issueComment); + } + + /** + * For Candidate Comments + */ + public CommentModel(CandidateComment candidateComment) { + initialize(candidateComment); + for (CandidateCommentRating issueRating : candidateComment.getUserRating()) { + updateRatingInformation(issueRating.getUser(), issueRating.getRating()); + } + } + + public static CommentModel from(CandidateComment candidateComment) { + return new CommentModel(candidateComment); + } + + public int compareTo(CommentModel o) + { + return(rating - o.rating); + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/EvidenceModel.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/EvidenceModel.java new file mode 100644 index 0000000..3afe496 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/EvidenceModel.java @@ -0,0 +1,94 @@ +package io.github.patternatlas.api.rest.model.shared; + +import io.github.patternatlas.api.entities.candidate.comment.CandidateComment; +import io.github.patternatlas.api.entities.candidate.comment.CandidateCommentRating; +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidence; +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidenceRating; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; +import io.github.patternatlas.api.entities.issue.comment.IssueCommentRating; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidence; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidenceRating; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class EvidenceModel { + + private UUID id; + private UUID userId; + private String userName; + private String title; + private String context; + private String type; + private Boolean supporting; + private String source; + private int rating; + private Collection upVotes = new ArrayList<>(); + private Collection downVotes = new ArrayList<>(); + + /** + * For Issue Comments + */ + public EvidenceModel(IssueEvidence issueEvidence) { + this.id = issueEvidence.getId(); + this.userId = issueEvidence.getUser().getId(); + this.userName = issueEvidence.getUser().getName(); + this.title = issueEvidence.getTitle(); + this.context = issueEvidence.getContext(); + this.type = issueEvidence.getType(); + this.supporting = issueEvidence.getSupporting(); + this.source = issueEvidence.getSource(); + this.rating = 0; + for (IssueEvidenceRating issueRating : issueEvidence.getUserRating()) { + if (issueRating.getRating() == 1) { + this.upVotes.add(issueRating.getUser().getId()); + this.rating = this.rating + 1; + } + if (issueRating.getRating() == -1) { + this.downVotes.add(issueRating.getUser().getId()); + this.rating = this.rating - 1; + } + } + } + + public static EvidenceModel from(IssueEvidence issueEvidence) { + return new EvidenceModel(issueEvidence); + } + + /** + * For Candidate Evidence + */ + public EvidenceModel(CandidateEvidence candidateEvidence) { + this.id = candidateEvidence.getId(); + this.userId = candidateEvidence.getUser().getId(); + this.userName = candidateEvidence.getUser().getName(); + this.title = candidateEvidence.getTitle(); + this.context = candidateEvidence.getContext(); + this.type = candidateEvidence.getType(); + this.supporting = candidateEvidence.getSupporting(); + this.source = candidateEvidence.getSource(); + this.rating = 0; + for (CandidateEvidenceRating candidateEvidenceRating : candidateEvidence.getUserRating()) { + if (candidateEvidenceRating.getRating() == 1) { + this.upVotes.add(candidateEvidenceRating.getUser().getId()); + this.rating = this.rating + 1; + } + if (candidateEvidenceRating.getRating() == -1) { + this.downVotes.add(candidateEvidenceRating.getUser().getId()); + this.rating = this.rating - 1; + } + } + } + + public static EvidenceModel from(CandidateEvidence candidateEvidence) { + return new EvidenceModel(candidateEvidence); + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/PatternLanguageSchemaModel.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/PatternLanguageSchemaModel.java new file mode 100644 index 0000000..ff70b45 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/PatternLanguageSchemaModel.java @@ -0,0 +1,33 @@ +package io.github.patternatlas.api.rest.model.shared; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.UUID; +import java.util.stream.Collectors; + +import io.github.patternatlas.api.entities.PatternLanguage; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class PatternLanguageSchemaModel { + + private UUID patternLanguageId; + private String patternLanguageName; + Collection patternSchema = new ArrayList<>(); + + public PatternLanguageSchemaModel(PatternLanguage patternLanguage) { + this.patternLanguageId = patternLanguage.getId(); + this.patternLanguageName = patternLanguage.getName(); + this.patternSchema = patternLanguage.getPatternSchema().getPatternSectionSchemas().stream().map(patternSectionSchema -> new PatternSchemaModel(patternSectionSchema)) + .collect(Collectors.toList()) + .stream().sorted(Comparator.comparing(PatternSchemaModel::getPosition)). + collect(Collectors.toList()); + } + +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/PatternSchemaModel.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/PatternSchemaModel.java new file mode 100644 index 0000000..5e7f648 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/PatternSchemaModel.java @@ -0,0 +1,32 @@ +package io.github.patternatlas.api.rest.model.shared; + +import io.github.patternatlas.api.entities.PatternSection; +import io.github.patternatlas.api.entities.PatternSectionSchema; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class PatternSchemaModel implements Comparable { + + private String label; + private String name; + private String type; + private Integer position; + + public PatternSchemaModel(PatternSectionSchema patternSectionSchema) { + this.label = patternSectionSchema.getLabel(); + this.name = patternSectionSchema.getName(); + this.type = patternSectionSchema.getType(); + this.position = patternSectionSchema.getPosition(); + } + + public int compareTo(PatternSchemaModel o) + { + return Integer.compare(this.position, o.position); + } + +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingModel.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingModel.java new file mode 100644 index 0000000..cf28557 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingModel.java @@ -0,0 +1,53 @@ +package io.github.patternatlas.api.rest.model.shared; + +import io.github.patternatlas.api.entities.candidate.CandidateRating; +import io.github.patternatlas.api.entities.candidate.comment.CandidateCommentRating; +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidenceRating; +import io.github.patternatlas.api.entities.issue.IssueRating; +import io.github.patternatlas.api.entities.issue.comment.IssueCommentRating; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidenceRating; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class RatingModel { + + private int rating; + private UUID userId; + + public RatingModel(IssueRating issueRating) { + this.rating = issueRating.getRating(); + this.userId = issueRating.getUser().getId(); + } + + public RatingModel(IssueCommentRating issueCommentRating) { + this.rating = issueCommentRating.getRating(); + this.userId = issueCommentRating.getUser().getId(); + } + + public RatingModel(IssueEvidenceRating issueEvidenceRating) { + this.rating = issueEvidenceRating.getRating(); + this.userId = issueEvidenceRating.getUser().getId(); + } + + public RatingModel(CandidateRating candidateRating, int rating) { + this.rating = rating; + this.userId = candidateRating.getUser().getId(); + } + + public RatingModel(CandidateCommentRating candidateCommentRating) { + this.rating = candidateCommentRating.getRating(); + this.userId = candidateCommentRating.getUser().getId(); + } + + public RatingModel(CandidateEvidenceRating candidateEvidenceRating) { + this.rating = candidateEvidenceRating.getRating(); + this.userId = candidateEvidenceRating.getUser().getId(); + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingModelMultiRequest.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingModelMultiRequest.java new file mode 100644 index 0000000..26d9846 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingModelMultiRequest.java @@ -0,0 +1,13 @@ +package io.github.patternatlas.api.rest.model.shared; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class RatingModelMultiRequest extends RatingModelRequest { + + private RatingType ratingType; +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingModelRequest.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingModelRequest.java new file mode 100644 index 0000000..f5badb8 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingModelRequest.java @@ -0,0 +1,13 @@ +package io.github.patternatlas.api.rest.model.shared; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class RatingModelRequest { + + private int rating; +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingType.java b/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingType.java new file mode 100644 index 0000000..c4ac5db --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/shared/RatingType.java @@ -0,0 +1,7 @@ +package io.github.patternatlas.api.rest.model.shared; + +public enum RatingType { + READABILITY, + UNDERSTANDABILITY, + APPROPRIATENESS; +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/user/PrivilegeModel.java b/src/main/java/io/github/patternatlas/api/rest/model/user/PrivilegeModel.java new file mode 100644 index 0000000..cd5d05f --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/user/PrivilegeModel.java @@ -0,0 +1,24 @@ +package io.github.patternatlas.api.rest.model.user; + +import io.github.patternatlas.api.entities.user.role.Privilege; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class PrivilegeModel { + + private UUID id; + + private String name; + + public PrivilegeModel(Privilege privilege) { + this.id = privilege.getId(); + this.name = privilege.getName(); + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/user/RoleModel.java b/src/main/java/io/github/patternatlas/api/rest/model/user/RoleModel.java new file mode 100644 index 0000000..f9b1814 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/user/RoleModel.java @@ -0,0 +1,28 @@ +package io.github.patternatlas.api.rest.model.user; + +import io.github.patternatlas.api.entities.user.role.Role; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.Collection; +import java.util.UUID; +import java.util.stream.Collectors; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class RoleModel { + + private UUID id; + private String name; + private Collection privileges; + + public RoleModel (Role role) { + this.id = role.getId(); + this.name = role.getName(); + this.privileges = role.getPrivileges().stream().map(privilege -> privilege.getName()).collect(Collectors.toList()); + + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/user/RoleModelRequest.java b/src/main/java/io/github/patternatlas/api/rest/model/user/RoleModelRequest.java new file mode 100644 index 0000000..6f32e86 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/user/RoleModelRequest.java @@ -0,0 +1,13 @@ +package io.github.patternatlas.api.rest.model.user; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class RoleModelRequest { + + private boolean checkboxValue; +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/user/UserModel.java b/src/main/java/io/github/patternatlas/api/rest/model/user/UserModel.java new file mode 100644 index 0000000..4290943 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/user/UserModel.java @@ -0,0 +1,57 @@ +package io.github.patternatlas.api.rest.model.user; + +import io.github.patternatlas.api.entities.user.UserEntity; +import io.github.patternatlas.api.rest.model.candidate.CandidateModel; +import io.github.patternatlas.api.rest.model.issue.IssueModel; +import io.github.patternatlas.api.rest.model.shared.CommentModel; +import io.github.patternatlas.api.rest.model.shared.EvidenceModel; +import io.github.patternatlas.api.rest.model.shared.RatingModel; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class UserModel { + + private UUID id; + private List roles; + private String email; + private String name; + private List issues; + private List issueComments; + private List issueRatings; + private List issueEvidences; + private List candidates; + private List candidateComments; + private List candidatesRatings; + private List candidatesEvidences; + private List patterns = new ArrayList<>(); + private List patternComments = new ArrayList<>(); + private List patternRatings = new ArrayList<>(); + private List patternEvidences = new ArrayList<>(); + + public UserModel(UserEntity user) { + this.id = user.getId(); + this.roles = user.getRoles().stream().map(role -> new RoleModel(role)).collect(Collectors.toList()); + this.email = user.getEmail(); + this.name = user.getName(); + // ISSUE + this.issues = user.getIssues().stream().map(issueAuthor -> new IssueModel(issueAuthor.getIssue())).collect(Collectors.toList()); + this.issueComments = user.getIssueComments().stream().map(issueComment -> new CommentModel(issueComment)).collect(Collectors.toList()); + this.issueRatings = user.getIssueRatings().stream().map((issueRating -> new IssueModel(issueRating.getIssue()))).collect(Collectors.toList()); + this.issueEvidences = user.getIssueEvidence().stream().map(issueEvidence -> new EvidenceModel(issueEvidence)).collect(Collectors.toList()); + // CANDIDATE + this.candidates = user.getCandidates().stream().map(candidateAuthor -> new CandidateModel(candidateAuthor.getCandidate())).collect(Collectors.toList()); + this.candidateComments = user.getCandidateComments().stream().map(candidateComment -> new CommentModel(candidateComment)).collect(Collectors.toList()); + this.candidatesRatings = user.getCandidateRatings().stream().map((candidateRating -> new CandidateModel(candidateRating.getCandidate()))).collect(Collectors.toList()); + this.candidatesEvidences = user.getCandidateEvidence().stream().map(candidateEvidence -> new EvidenceModel(candidateEvidence)).collect(Collectors.toList()); + } +} diff --git a/src/main/java/io/github/patternatlas/api/rest/model/user/UserModelRequest.java b/src/main/java/io/github/patternatlas/api/rest/model/user/UserModelRequest.java new file mode 100644 index 0000000..6b5a643 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/rest/model/user/UserModelRequest.java @@ -0,0 +1,14 @@ +package io.github.patternatlas.api.rest.model.user; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = false) +public class UserModelRequest extends UserModel { + + private String oldPassword; + private String password; +} diff --git a/src/main/java/io/github/patternatlas/api/security/ResourceMethodSecurityExpressionHandler.java b/src/main/java/io/github/patternatlas/api/security/ResourceMethodSecurityExpressionHandler.java new file mode 100644 index 0000000..c891e6f --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/security/ResourceMethodSecurityExpressionHandler.java @@ -0,0 +1,66 @@ +package io.github.patternatlas.api.security; + +import io.github.patternatlas.api.repositories.RoleRepository; +import io.github.patternatlas.api.service.RoleService; +import io.github.patternatlas.api.service.UserAuthService; +import io.github.patternatlas.api.service.UserService; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.context.ApplicationContext; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.hateoas.CollectionModel; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.AuthenticationTrustResolverImpl; +import org.springframework.security.core.Authentication; + +import java.util.LinkedList; + +public class ResourceMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { + private ApplicationContext applicationContext; + private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + + /** Overrides filter to support filtering CollectionModel as it is used in API */ + @Override + public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) { + if(filterTarget instanceof CollectionModel) { + // The collection has to be extracted so filter can work appropriately + /* From the Spring-Documentation + "If a Collection or Map is used, the original instance will be modified + to contain the elements for which the permission expression evaluates to true." + => super.filter should return a collection again, which can be added to + CollectionModel + */ + CollectionModel filterCollectionModel = (CollectionModel)filterTarget; + // Since the Collection obtained from getContent() is immutable, + // a new List has to be created to be filtered + Object filterResult = super.filter(new LinkedList<>(filterCollectionModel.getContent()), filterExpression, ctx); + return CollectionModel.of(filterResult); + } + // Default case - proceed as usual + return super.filter(filterTarget, filterExpression, ctx); + } + + @Override + protected MethodSecurityExpressionOperations createSecurityExpressionRoot( + Authentication authentication, MethodInvocation invocation) { + ResourceSecurityExpressionRoot root = new ResourceSecurityExpressionRoot( + authentication, + this.applicationContext.getBean(UserService.class), + this.applicationContext.getBean(RoleService.class), + this.applicationContext.getBean(RoleRepository.class), + this.applicationContext.getBean(UserAuthService.class)); + + root.setPermissionEvaluator(getPermissionEvaluator()); + root.setTrustResolver(this.trustResolver); + root.setRoleHierarchy(getRoleHierarchy()); + return root; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + super.setApplicationContext(applicationContext); + this.applicationContext = applicationContext; + } +} diff --git a/src/main/java/io/github/patternatlas/api/security/ResourceSecurityExpressionRoot.java b/src/main/java/io/github/patternatlas/api/security/ResourceSecurityExpressionRoot.java new file mode 100644 index 0000000..e22c974 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/security/ResourceSecurityExpressionRoot.java @@ -0,0 +1,174 @@ +package io.github.patternatlas.api.security; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.patternatlas.api.entities.user.UserEntity; +import io.github.patternatlas.api.entities.user.role.Role; +import io.github.patternatlas.api.entities.user.role.RoleConstant; +import io.github.patternatlas.api.exception.UserNotFoundException; +import io.github.patternatlas.api.repositories.RoleRepository; +import io.github.patternatlas.api.rest.model.user.RoleModel; +import io.github.patternatlas.api.rest.model.user.UserModelRequest; +import io.github.patternatlas.api.service.RoleService; +import io.github.patternatlas.api.service.UserAuthService; +import io.github.patternatlas.api.service.UserService; +import io.github.patternatlas.api.service.UserServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.expression.SecurityExpressionRoot; +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; +import org.springframework.security.core.Authentication; +import org.springframework.security.jwt.Jwt; +import org.springframework.security.jwt.JwtHelper; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; +import org.springframework.security.oauth2.provider.token.TokenStore; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +public class ResourceSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { + + private Object filterObject; + private Object returnObject; + private UserAuthService userAuthService; + private UserService userService; + private RoleService roleService; + private RoleRepository roleRepository; + + public ResourceSecurityExpressionRoot(Authentication authentication, + UserService userService, + RoleService roleService, + RoleRepository roleRepository, + UserAuthService userAuthService) { + super(authentication); + this.userService = userService; + this.roleService = roleService; + this.roleRepository = roleRepository; + this.userAuthService = userAuthService; + } + + /** + * Checks global permission for user. + * Will only check for the exact permission (e.g. ISSUE_CREATE) + * @param permissionType type of the permission (ISSUE_CREATE) + * @return true if user has permission + */ + public boolean hasGlobalPermission(String permissionType) { + Optional userId = loggedInUUID(); + + if (userId.isPresent()) { + return this.userService.hasAnyPrivilege( + userId.get(), + permissionType); + } else { + return this.roleService.hasAnyPrivilege( + roleRepository.findByName(RoleConstant.GUEST).getId(), + permissionType + ); + } + } + + private void createUser(UUID userId, Authentication authentication) { + OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) this.getAuthentication().getDetails(); + Jwt jwt = JwtHelper.decode(details.getTokenValue()); + ObjectMapper objectMapper = new ObjectMapper(); + try { + Map claims = objectMapper.readValue(jwt.getClaims(), Map.class); + String preferredUsername = claims.get("preferred_username").toString(); + UserModelRequest req = new UserModelRequest(); + req.setId(userId); + req.setName(preferredUsername); + + if(this.userAuthService.hasUsers()) { + // Create standard member user + this.userAuthService.createInitialMember(req); + } else { + // There are no other users registered => create admin user as first user + this.userAuthService.createInitialAdmin(req); + } + } catch (JsonProcessingException e) { + throw new UserNotFoundException("Cannot infer preferred username from Token"); + } + } + + public Optional loggedInUUID() { + if (this.getAuthentication() == null) { + return Optional.empty(); + } + + try { + // Check if user exists in patternatlas + UUID userId = UUID.fromString(this.getAuthentication().getName()); // Supplied through JWT id field + if(!this.userAuthService.userExists(userId)) { + // The user is properly authenticated but is not yet created in patternatlas db + createUser(userId, this.getAuthentication()); + } + return Optional.of(userId); + } catch (IllegalArgumentException exception) { + return Optional.empty(); + } + } + + /** + * Checks permission given the objects UUID. + * Will check for general version of the permission (e.g. ISSUE_READ_ALL) + * and for resource specific version (e.g. ISSUE_READ_[uuid]) + * @param resource resource uuid + * @param permissionType type of the permission (ISSUE_READ) + * @return true if user has permission + */ + public boolean hasResourcePermission(UUID resource, String permissionType) { + Optional userId = loggedInUUID(); + + if (userId.isPresent()) { + return this.userService.hasAnyPrivilege( + userId.get(), + permissionType + "_ALL", + permissionType + "_" + resource.toString()); + } else { + return this.roleService.hasAnyPrivilege( + roleRepository.findByName(RoleConstant.GUEST).getId(), + permissionType + "_ALL", + permissionType + "_" + resource.toString() + ); + } + } + + public void setUserAuthService(UserAuthService userAuthService) { + this.userAuthService = userAuthService; + } + + public void setUserService(UserService userService) { + this.userService = userService; + } + + @Override + public void setFilterObject(Object o) { + this.filterObject = o; + } + + @Override + public Object getFilterObject() { + return this.filterObject; + } + + @Override + public void setReturnObject(Object o) { + this.returnObject = o; + } + + @Override + public Object getReturnObject() { + return this.returnObject; + } + + @Override + public Object getThis() { + return this; + } +} diff --git a/src/main/java/io/github/patternatlas/api/service/AuthorityService.java b/src/main/java/io/github/patternatlas/api/service/AuthorityService.java new file mode 100644 index 0000000..161a64e --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/service/AuthorityService.java @@ -0,0 +1,23 @@ +package io.github.patternatlas.api.service; + +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +@Service("authorityService") +public class AuthorityService { + + + + /** + * @param baseAuthority base authority e.g. ISSUE_EDIT + * @param resource Resource for creating resource specific authority + * @return List of authorities allowing to perform action on resource (e.g. ISSUE_EDIT_ALL, ISSUE_EDIT_[uuid]) + */ + public List formatResourceAuthorities(String baseAuthority, UUID resource){ + List authorities = Arrays.asList(baseAuthority + "_ALL", baseAuthority + "_" + resource.toString()); + return authorities; + } +} diff --git a/src/main/java/io/github/patternatlas/api/service/CandidateService.java b/src/main/java/io/github/patternatlas/api/service/CandidateService.java index 01da193..f90b6a2 100644 --- a/src/main/java/io/github/patternatlas/api/service/CandidateService.java +++ b/src/main/java/io/github/patternatlas/api/service/CandidateService.java @@ -3,39 +3,83 @@ import java.util.List; import java.util.UUID; -import io.github.patternatlas.api.entities.candidate.CandidateComment; -import io.github.patternatlas.api.rest.model.CandidateModel; import io.github.patternatlas.api.entities.candidate.Candidate; +import io.github.patternatlas.api.entities.candidate.comment.CandidateComment; +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidence; +import io.github.patternatlas.api.rest.model.candidate.CandidateModelRequest; +import io.github.patternatlas.api.rest.model.shared.AuthorModelRequest; +import io.github.patternatlas.api.rest.model.shared.CommentModel; +import io.github.patternatlas.api.rest.model.shared.EvidenceModel; +import io.github.patternatlas.api.rest.model.shared.RatingModelMultiRequest; +import io.github.patternatlas.api.rest.model.shared.RatingModelRequest; + +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; public interface CandidateService { - /** - * CRUD - */ - Candidate createCandidate(CandidateModel candidateModel); + Candidate saveCandidate(Candidate candidate); + /** CRUD */ + @PreAuthorize(value = "hasGlobalPermission(@PC.PATTERN_CANDIDATE_CREATE)") + Candidate createCandidate(CandidateModelRequest candidateModelRequest, UUID userId); - Candidate updateCandidate(Candidate candidate); + @PostFilter("hasResourcePermission(filterObject.id, @PC.PATTERN_CANDIDATE_READ)") + List getAllCandidates(); - void deleteCandidate(UUID candidateId); + @PostFilter("hasResourcePermission(filterObject.id, @PC.PATTERN_CANDIDATE_READ)") + List getAllCandidatesByLanguageId(UUID languageId); + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_READ)") Candidate getCandidateById(UUID candidateId); + @PostAuthorize(value = "hasResourcePermission(returnObject.id, @PC.PATTERN_CANDIDATE_READ)") Candidate getCandidateByURI(String uri); - List getAllCandidates(); + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_EDIT)") + Candidate updateCandidate(UUID candidateId, UUID userId, CandidateModelRequest candidateModelRequest); + + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_VOTE)") + Candidate updateCandidateRating(UUID candidateId, UUID userId, RatingModelMultiRequest ratingModelMultiRequest); + + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_DELETE)") + void deleteCandidate(UUID candidateId, UUID userId); + + /** Author */ + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_EDIT)") + Candidate saveCandidateAuthor(UUID candidateId, AuthorModelRequest authorModelRequest); - /** - * Voting - */ - Candidate userRating(UUID candidateId, UUID userId, String rating); + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_EDIT)") + Candidate deleteCandidateAuthor(UUID candidateId, UUID userId); - /** - * Comment - */ - Candidate createComment(UUID candidateId, UUID userId, CandidateComment candidateComment); + /** Comment */ + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_COMMENT)") + Candidate createComment(UUID candidateId, UUID userId, CommentModel commentModel); + @PostAuthorize(value = "hasResourcePermission(returnObject.candidate.id, @PC.PATTERN_CANDIDATE_READ)") CandidateComment getCommentById(UUID candidateCommentId); - CandidateComment updateComment(CandidateComment candidateComment); + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_EDIT)") + Candidate updateComment(UUID candidateId, UUID commentId, UUID userId, CommentModel commentModel); + + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_VOTE)") + Candidate updateCandidateCommentRating(UUID candidateId, UUID commentId, UUID userId, RatingModelRequest ratingModelRequest); + + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_EDIT)") + Candidate deleteComment(UUID candidateId, UUID commentId, UUID userId); + + /** Evidence */ + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_EVIDENCE)") + Candidate createEvidence(UUID candidateId, UUID userId, EvidenceModel evidenceModel); + + @PostAuthorize(value = "hasResourcePermission(returnObject.candidate.id, @PC.PATTERN_CANDIDATE_READ)") + CandidateEvidence getEvidenceById(UUID evidenceId); + + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_EDIT)") + Candidate updateEvidence(UUID candidateId, UUID evidenceId, UUID userId, EvidenceModel evidenceModel); + + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_VOTE)") + Candidate updateCandidateEvidenceRating(UUID candidateId, UUID evidenceID, UUID userId, RatingModelRequest ratingModelRequest); - Candidate commentUserRating(UUID candidateCommentId, UUID userId, String rating); -} + @PreAuthorize(value = "hasResourcePermission(#candidateId, @PC.PATTERN_CANDIDATE_EDIT)") + Candidate deleteEvidence(UUID candidateId, UUID evidenceId, UUID userId); +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/service/CandidateServiceImpl.java b/src/main/java/io/github/patternatlas/api/service/CandidateServiceImpl.java index da1f3c4..9ef478c 100644 --- a/src/main/java/io/github/patternatlas/api/service/CandidateServiceImpl.java +++ b/src/main/java/io/github/patternatlas/api/service/CandidateServiceImpl.java @@ -1,54 +1,102 @@ package io.github.patternatlas.api.service; -import java.util.List; -import java.util.UUID; +import io.github.patternatlas.api.entities.candidate.Candidate; +import io.github.patternatlas.api.entities.candidate.CandidateRating; +import io.github.patternatlas.api.entities.candidate.comment.CandidateComment; +import io.github.patternatlas.api.entities.candidate.author.CandidateAuthor; +import io.github.patternatlas.api.entities.candidate.comment.CandidateCommentRating; +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidence; +import io.github.patternatlas.api.entities.candidate.evidence.CandidateEvidenceRating; +import io.github.patternatlas.api.entities.issue.Issue; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidence; +import io.github.patternatlas.api.entities.shared.AuthorConstant; +import io.github.patternatlas.api.entities.user.UserEntity; +import io.github.patternatlas.api.entities.user.role.Privilege; +import io.github.patternatlas.api.entities.user.role.PrivilegeConstant; +import io.github.patternatlas.api.entities.user.role.Role; +import io.github.patternatlas.api.entities.user.role.RoleConstant; +import io.github.patternatlas.api.repositories.CandidateAuthorRepository; +import io.github.patternatlas.api.repositories.CandidateCommentRatingRepository; +import io.github.patternatlas.api.repositories.CandidateCommentRepository; +import io.github.patternatlas.api.repositories.CandidateEvidenceRatingRepository; +import io.github.patternatlas.api.repositories.CandidateEvidenceRepository; +import io.github.patternatlas.api.repositories.CandidateRatingRepository; +import io.github.patternatlas.api.repositories.CandidateRepository; +import io.github.patternatlas.api.rest.model.candidate.CandidateModelRequest; +import io.github.patternatlas.api.rest.model.shared.AuthorModel; +import io.github.patternatlas.api.rest.model.shared.AuthorModelRequest; +import io.github.patternatlas.api.rest.model.shared.CommentModel; +import io.github.patternatlas.api.rest.model.shared.EvidenceModel; +import io.github.patternatlas.api.rest.model.shared.RatingModelMultiRequest; +import io.github.patternatlas.api.rest.model.shared.RatingModelRequest; +import io.github.patternatlas.api.rest.model.shared.RatingType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import io.github.patternatlas.api.entities.candidate.CandidateComment; -import io.github.patternatlas.api.exception.CandidateNotFoundException; -import io.github.patternatlas.api.exception.NullCandidateException; -import io.github.patternatlas.api.repositories.CandidateCommentRatingRepository; -import io.github.patternatlas.api.repositories.CandidateCommentRepository; -import io.github.patternatlas.api.repositories.CandidateRatingRepository; -import io.github.patternatlas.api.repositories.CandidateRepository; -import io.github.patternatlas.api.rest.model.CandidateModel; -import io.github.patternatlas.api.util.RatingHelper; -import io.github.patternatlas.api.entities.PatternLanguage; -import io.github.patternatlas.api.entities.candidate.Candidate; +import javax.persistence.EntityExistsException; +import javax.persistence.EntityNotFoundException; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; @Service @Transactional public class CandidateServiceImpl implements CandidateService { - private final CandidateRepository candidateRepository; - private final CandidateRatingRepository candidateRatingRepository; - private final CandidateCommentRepository candidateCommentRepository; - private final CandidateCommentRatingRepository candidateCommentRatingRepository; - private final PatternLanguageService patternLanguageService; - private final UserService userService; - private final RatingHelper ratingHelper; + private CandidateRepository candidateRepository; + private CandidateRatingRepository candidateRatingRepository; + private CandidateAuthorRepository candidateAuthorRepository; + private CandidateCommentRepository candidateCommentRepository; + private CandidateCommentRatingRepository candidateCommentRatingRepository; + private CandidateEvidenceRepository candidateEvidenceRepository; + private CandidateEvidenceRatingRepository candidateEvidenceRatingRepository; + private PatternLanguageService patternLanguageService; + private UserService userService; + private RoleService roleService; + private PrivilegeService privilegeService; + private IssueService issueService; - Logger logger = LoggerFactory.getLogger(IssueServiceImpl.class); + Logger logger = LoggerFactory.getLogger(CandidateServiceImpl.class); public CandidateServiceImpl( CandidateRepository candidateRepository, CandidateRatingRepository candidateRatingRepository, + CandidateAuthorRepository candidateAuthorRepository, CandidateCommentRepository candidateCommentRepository, CandidateCommentRatingRepository candidateCommentRatingRepository, + CandidateEvidenceRepository candidateEvidenceRepository, + CandidateEvidenceRatingRepository candidateEvidenceRatingRepository, PatternLanguageService patternLanguageService, - UserService userService + UserService userService, + RoleService roleService, + PrivilegeService privilegeService, + IssueService issueService ) { this.candidateRepository = candidateRepository; this.candidateRatingRepository = candidateRatingRepository; + this.candidateAuthorRepository = candidateAuthorRepository; this.candidateCommentRepository = candidateCommentRepository; this.candidateCommentRatingRepository = candidateCommentRatingRepository; + this.candidateEvidenceRepository = candidateEvidenceRepository; + this.candidateEvidenceRatingRepository = candidateEvidenceRatingRepository; this.patternLanguageService = patternLanguageService; this.userService = userService; - this.ratingHelper = new RatingHelper(); + this.roleService = roleService; + this.privilegeService = privilegeService; + this.issueService = issueService; + } + + @Override + @Transactional + public Candidate saveCandidate(Candidate candidate) { + return this.candidateRepository.save(candidate); } /** @@ -56,95 +104,352 @@ public CandidateServiceImpl( */ @Override @Transactional - public Candidate createCandidate(CandidateModel candidateModel) { - Candidate candidate = new Candidate(candidateModel); + public Candidate createCandidate(CandidateModelRequest candidateModelRequest, UUID userId) { + Candidate candidate = new Candidate(candidateModelRequest); + UserEntity user = this.userService.getUserById(userId); + if (null == candidate) + throw new RuntimeException("Candidate to create is null"); + if (this.candidateRepository.existsByName(candidate.getName())) + throw new EntityExistsException(String.format("Candidate name %s already exist!", candidateModelRequest.getName())); + if (this.candidateRepository.existsByUri(candidateModelRequest.getUri())) + throw new EntityExistsException(String.format("Candidate uri %s already exist!", candidateModelRequest.getUri())); - if (null == candidate) { - throw new NullCandidateException(); - } + // ISSUE TO PATTERN + Candidate newCandidate = this.candidateRepository.save(candidate); + + Privilege readCandidatePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.PATTERN_CANDIDATE_READ + '_' + newCandidate.getId()); + Privilege updateCandidatePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.PATTERN_CANDIDATE_EDIT + '_' + newCandidate.getId()); + Privilege deleteCandidatePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.PATTERN_CANDIDATE_DELETE + '_' + newCandidate.getId()); + Privilege commentCandidatePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.PATTERN_CANDIDATE_COMMENT + '_' + newCandidate.getId()); + Privilege voteCandidatePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.PATTERN_CANDIDATE_VOTE + '_' + newCandidate.getId()); + Privilege evidenceCandidatePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.PATTERN_CANDIDATE_EVIDENCE + '_' + newCandidate.getId()); + Privilege toApprovedPattern = this.privilegeService.createPrivilege(PrivilegeConstant.PATTERN_CANDIDATE_TO_PATTERN + '_' + newCandidate.getId()); - if (candidateModel.getPatternLanguageId() != null) { - PatternLanguage patternLanguage = this.patternLanguageService.getPatternLanguageById(candidateModel.getPatternLanguageId()); - candidate.setPatternLanguage(patternLanguage); + Role helper = this.roleService.createRole(RoleConstant.HELPER + "_PATTERN_CANDIDATE_" + newCandidate.getId(), Arrays.asList( + readCandidatePrivilege, + commentCandidatePrivilege, voteCandidatePrivilege, evidenceCandidatePrivilege + )); + Role maintainer = this.roleService.createRole(RoleConstant.MAINTAINER + "_PATTERN_CANDIDATE_" + newCandidate.getId(), Arrays.asList( + readCandidatePrivilege, updateCandidatePrivilege, + commentCandidatePrivilege, voteCandidatePrivilege, evidenceCandidatePrivilege + )); + Role owner = this.roleService.createRole(RoleConstant.OWNER + "_PATTERN_CANDIDATE_" + newCandidate.getId(), Arrays.asList( + readCandidatePrivilege, updateCandidatePrivilege, deleteCandidatePrivilege, toApprovedPattern, + commentCandidatePrivilege, voteCandidatePrivilege, evidenceCandidatePrivilege + )); + + if (candidateModelRequest.getIssueId() != null) { + logger.info("Issue to Candidate request"); + Issue issue = this.issueService.getIssueById(candidateModelRequest.getIssueId()); + for (IssueEvidence issueEvidence: issue.getEvidences()) { + CandidateEvidence evidence = new CandidateEvidence(issueEvidence, newCandidate, user); + newCandidate.getEvidences().add(evidence); + } + this.issueService.deleteIssue(candidateModelRequest.getIssueId()); + } + // ADD author + if (candidateModelRequest.getAuthors() != null) { + for (AuthorModel authorModel : candidateModelRequest.getAuthors()) { + newCandidate.getAuthors().add(new CandidateAuthor(newCandidate, this.userService.getUserById(authorModel.getUserId()), authorModel.getAuthorRole() + "_PATTERN_CANDIDATE_" + newCandidate.getId())); + UserEntity u = this.userService.getUserById(authorModel.getUserId()); + switch (authorModel.getAuthorRole()) { + case AuthorConstant.HELPER: + user.getRoles().add(helper); + this.userService.saveUser(u); + break; + case AuthorConstant.MAINTAINER: + user.getRoles().add(maintainer); + this.userService.saveUser(u); + break; + case AuthorConstant.OWNER: + if (null != u.getRoles()) { + u.getRoles().add(owner); + } else { + u.setRoles(new HashSet<>(Arrays.asList(owner))); + } + this.userService.saveUser(u); + break; + default: + throw new IllegalArgumentException("Invalid author role: " + authorModel.getAuthorRole()); + } + } } else { - candidate.setPatternLanguage(null); + newCandidate.getAuthors().add(new CandidateAuthor(newCandidate, this.userService.getUserById(userId), AuthorConstant.OWNER)); + user.getRoles().add(owner); } - Candidate newCandidate = this.candidateRepository.save(candidate); - logger.info(String.format("Create Candidate %s: ", newCandidate.toString())); - return newCandidate; + // ADD pattern language + if (candidateModelRequest.getPatternLanguageId() != null && this.patternLanguageService.getPatternLanguageById(candidateModelRequest.getPatternLanguageId()) != null) + newCandidate.setPatternLanguage(this.patternLanguageService.getPatternLanguageById(candidateModelRequest.getPatternLanguageId())); + + return this.candidateRepository.save(newCandidate); + } + + @Override + @Transactional + public List getAllCandidates() { + return this.candidateRepository.findAll(); + } + + @Override + @Transactional + public List getAllCandidatesByLanguageId(UUID languageId) { + return this.candidateRepository.findAllByLanguageId(languageId); } @Override @Transactional public Candidate getCandidateById(UUID candidateId) { return this.candidateRepository.findById(candidateId) - .orElseThrow(() -> new CandidateNotFoundException(candidateId)); + .orElseThrow(() -> new ResourceNotFoundException(String.format("Candidate with ID %s not found!", candidateId))); } @Override @Transactional public Candidate getCandidateByURI(String uri) { return this.candidateRepository.findByUri(uri) - .orElseThrow(() -> new CandidateNotFoundException(String.format("Pattern with URI %s not found!", uri))); + .orElseThrow(() -> new ResourceNotFoundException(String.format("Candidate with URI %s not found!", uri))); } @Override @Transactional - public List getAllCandidates() { - return this.candidateRepository.findAll(); + public Candidate updateCandidate(UUID candidateId, UUID userId, CandidateModelRequest candidateModelRequest) { + if (candidateModelRequest == null) { + throw new RuntimeException("Candidate to update is null!"); + } + Candidate candidate = this.getCandidateById(candidateId); + UserEntity user = this.userService.getUserById(userId); + + if (this.candidateRepository.existsByUri(candidateModelRequest.getUri())) { + Candidate candidateByURI = this.getCandidateByURI(candidateModelRequest.getUri()); + // NOT uri & name change + if (!candidateByURI.getId().equals(candidate.getId())) { + throw new EntityExistsException(String.format("Candidate uri %s already exist!", candidateModelRequest.getUri())); + } + } + // ADD pattern language + if (candidateModelRequest.getPatternLanguageId() != null && this.patternLanguageService.getPatternLanguageById(candidateModelRequest.getPatternLanguageId()) != null) + candidate.setPatternLanguage(this.patternLanguageService.getPatternLanguageById(candidateModelRequest.getPatternLanguageId())); + + // UPDATE issue fields + candidate.updateCandidate(candidateModelRequest); + + return this.candidateRepository.save(candidate); } @Override @Transactional - public Candidate updateCandidate(Candidate candidate) { - if (null == candidate) { - throw new NullCandidateException(); + public Candidate updateCandidateRating(UUID candidateId, UUID userId, RatingModelMultiRequest ratingModelMultiRequest) { + Candidate candidate = this.getCandidateById(candidateId); + UserEntity user = this.userService.getUserById(userId); + CandidateRating candidateRating = new CandidateRating(candidate, user); + if (this.candidateRatingRepository.existsById(candidateRating.getId())) { + candidateRating = this.candidateRatingRepository.getOne(candidateRating.getId()); } - if (!this.candidateRepository.existsById(candidate.getId())) { - throw new CandidateNotFoundException(candidate.getId()); + if (ratingModelMultiRequest.getRatingType().equals(RatingType.READABILITY)) { + candidateRating.setReadability(ratingModelMultiRequest.getRating()); + + } else if (ratingModelMultiRequest.getRatingType().equals(RatingType.UNDERSTANDABILITY)) { + candidateRating.setUnderstandability(ratingModelMultiRequest.getRating()); + + } else if (ratingModelMultiRequest.getRatingType().equals(RatingType.APPROPRIATENESS)) { + candidateRating.setAppropriateness(ratingModelMultiRequest.getRating()); + + } else { + throw new EntityExistsException(String.format("Rating type does not exists", ratingModelMultiRequest.getRatingType())); } + CandidateRating updatedCandidateRating = this.candidateRatingRepository.save(candidateRating); + return updatedCandidateRating.getCandidate(); + } - logger.info(String.format("Update Issue: %s", candidate.toString())); - return this.candidateRepository.save(candidate); + @Override + @Transactional + public void deleteCandidate(UUID candidateId, UUID userId) { + UserEntity user = this.userService.getUserById(userId); + + this.roleService.deleteAllRolesByResourceId(candidateId); + this.privilegeService.deleteAllPrivilegesByResourceId(candidateId); + user.removeRole(candidateId.toString()); + this.candidateRepository.deleteById(candidateId); + + } + + /** + * Author + */ + @Override + @Transactional + public Candidate saveCandidateAuthor(UUID candidateId, AuthorModelRequest authorModelRequest) { + Candidate candidate = this.getCandidateById(candidateId); + UserEntity user = this.userService.getUserById(authorModelRequest.getUserId()); + CandidateAuthor candidateAuthor = new CandidateAuthor(candidate, user, authorModelRequest.getAuthorRole()); + candidateAuthor = this.candidateAuthorRepository.save(candidateAuthor); + + // Save roles associated with author + List authorRoles = this.roleService.findAllFromEntityForAuthorRole(candidate.getId(), + authorModelRequest.getAuthorRole()); + user.getRoles().addAll(authorRoles); + this.userService.saveUser(user); + + return candidateAuthor.getCandidate(); } @Override @Transactional - public void deleteCandidate(UUID candidateId) { + public Candidate deleteCandidateAuthor(UUID candidateId, UUID userId) { Candidate candidate = this.getCandidateById(candidateId); - if (null == candidate) { - throw new NullCandidateException(); + UserEntity user = this.userService.getUserById(userId); + CandidateAuthor candidateAuthor = new CandidateAuthor(candidate, user); + if (this.candidateAuthorRepository.existsById(candidateAuthor.getId())) { + List roles = this.userService.getAllRolesFromEntity(candidateId) // find all roles for candidate + .stream().filter((role) -> role.getUsers().contains(user)) // filter for roles for current user + .collect(Collectors.toList()); + + user.getRoles().removeAll(roles); // delete only those + + this.candidateAuthorRepository.deleteById(candidateAuthor.getId()); + this.userService.saveUser(user); } + return this.getCandidateById(candidateId); + } - this.candidateRepository.deleteById(candidateId); + /** + * Comment + */ + @Override + @Transactional + public Candidate createComment(UUID candidateId, UUID userId, CommentModel commentModel) { + Candidate candidate = this.getCandidateById(candidateId); + UserEntity user = this.userService.getUserById(userId); + + CandidateComment comment = new CandidateComment(commentModel.getText(), candidate, user); + comment = this.candidateCommentRepository.save(comment); + return comment.getCandidate(); + } + + @Override + @Transactional(readOnly = true) + public CandidateComment getCommentById(UUID candidateCommentId) { + return this.candidateCommentRepository.findById(candidateCommentId) + .orElseThrow(() -> new ResourceNotFoundException(String.format("Candidate comment with ID %s not found!", candidateCommentId))); + } + + @Override + @Transactional + public Candidate updateComment(UUID candidateId, UUID commentId, UUID userId, CommentModel commentModel) { + if (commentModel == null) + throw new RuntimeException("Candidate comment to update is null!"); + // Used to check if candidate actually exists + CandidateComment candidateComment = this.authCandidateComment(candidateId, commentId, userId); + + // UPDATE issue comment + candidateComment.updateComment(commentModel.getText()); + candidateComment = this.candidateCommentRepository.save(candidateComment); + return candidateComment.getCandidate(); + } + + @Override + @Transactional + public Candidate updateCandidateCommentRating(UUID candidateId, UUID commentId, UUID userId, RatingModelRequest ratingModelRequest) { + Candidate candidate = this.getCandidateById(candidateId); + CandidateComment candidateComment = this.getCommentById(commentId); + UserEntity user = this.userService.getUserById(userId); + CandidateCommentRating candidateCommentRating = new CandidateCommentRating(candidateComment, user, ratingModelRequest.getRating()); + if (this.candidateCommentRatingRepository.existsByIdAndRating(candidateCommentRating.getId(), candidateCommentRating.getRating())) { + candidateCommentRating.setRating(0); + } + candidateCommentRating = this.candidateCommentRatingRepository.save(candidateCommentRating); + return candidateCommentRating.getCandidateComment().getCandidate(); + } + + @Override + @Transactional + public Candidate deleteComment(UUID candidateId, UUID commentId, UUID userId) { + this.authCandidateComment(candidateId, commentId, userId); + this.candidateCommentRepository.deleteById(commentId); + return this.getCandidateById(candidateId); + } + + private CandidateComment authCandidateComment(UUID candidateId, UUID commentId, UUID userId) { + CandidateComment candidateComment = this.getCommentById(commentId); + // CORRECT Issue + if (!candidateComment.getCandidate().equals(this.getCandidateById(candidateId))) + throw new EntityNotFoundException(String.format("Candidate comment with id %s does not belong to candidate with id %s", commentId, candidateId)); + // CORRECT user + if (!candidateComment.getUser().equals(this.userService.getUserById(userId))) + throw new EntityNotFoundException(String.format("Candidate comment with id %s does not belong to user with id %s", commentId, userId)); + return candidateComment; } /** - * + * Evidence */ @Override - public Candidate userRating(UUID candidateId, UUID userId, String rating) { - return null; + @Transactional + public Candidate createEvidence(UUID candidateId, UUID userId, EvidenceModel evidenceModel) { + Candidate candidate = this.getCandidateById(candidateId); + UserEntity user = this.userService.getUserById(userId); + + CandidateEvidence candidateEvidence = new CandidateEvidence( + evidenceModel.getTitle(), + evidenceModel.getContext(), + evidenceModel.getType(), + evidenceModel.getSupporting(), + evidenceModel.getSource(), + candidate, user); + candidateEvidence = this.candidateEvidenceRepository.save(candidateEvidence); + return candidateEvidence.getCandidate(); } @Override - public Candidate createComment(UUID candidateId, UUID userId, CandidateComment candidateComment) { - return null; + @Transactional(readOnly = true) + public CandidateEvidence getEvidenceById(UUID evidenceId) { + return this.candidateEvidenceRepository.findById(evidenceId) + .orElseThrow(() -> new ResourceNotFoundException(String.format("Candidate evidence with ID %s not found!", evidenceId))); } @Override - public CandidateComment getCommentById(UUID candidateCommentId) { - return null; + @Transactional + public Candidate updateEvidence(UUID candidateId, UUID evidenceId, UUID userId, EvidenceModel evidenceModel) { + if (evidenceModel == null) + throw new RuntimeException("Candidate evidence to update is null!"); + CandidateEvidence candidateEvidence = this.existsCandidateEvidence(candidateId, evidenceId, userId); + // UPDATE Candidate evidence + candidateEvidence.updateEvidence(evidenceModel.getTitle(), evidenceModel.getContext(), evidenceModel.getType(), evidenceModel.getSupporting(), evidenceModel.getSource()); + candidateEvidence = this.candidateEvidenceRepository.save(candidateEvidence); + return candidateEvidence.getCandidate(); } @Override - public CandidateComment updateComment(CandidateComment candidateComment) { - return null; + @Transactional + public Candidate updateCandidateEvidenceRating(UUID candidateId, UUID evidenceID, UUID userId, RatingModelRequest ratingModelRequest) { + Candidate candidate = this.getCandidateById(candidateId); + CandidateEvidence candidateEvidence = this.getEvidenceById(evidenceID); + UserEntity user = this.userService.getUserById(userId); + CandidateEvidenceRating candidateEvidenceRating = new CandidateEvidenceRating(candidateEvidence, user, ratingModelRequest.getRating()); + if (this.candidateEvidenceRatingRepository.existsByIdAndRating(candidateEvidenceRating.getId(), candidateEvidenceRating.getRating())) { + candidateEvidenceRating.setRating(0); + } + candidateEvidenceRating = this.candidateEvidenceRatingRepository.save(candidateEvidenceRating); + return candidateEvidenceRating.getCandidateEvidence().getCandidate(); } @Override - public Candidate commentUserRating(UUID candidateCommentId, UUID userId, String rating) { - return null; + @Transactional + public Candidate deleteEvidence(UUID candidateId, UUID evidenceId, UUID userId) { + this.existsCandidateEvidence(candidateId, evidenceId, userId); + this.candidateEvidenceRepository.deleteById(evidenceId); + return this.getCandidateById(candidateId); + } + + private CandidateEvidence existsCandidateEvidence(UUID candidateId, UUID evidenceId, UUID userId) { + CandidateEvidence candidateEvidence = this.getEvidenceById(evidenceId); + // CORRECT Evidence + if (!candidateEvidence.getCandidate().equals(this.getCandidateById(candidateId))) + throw new EntityNotFoundException(String.format("Candidate comment with id %s does not belong to candidate with id %s", evidenceId, candidateId)); + // CORRECT user + if (!candidateEvidence.getUser().equals(this.userService.getUserById(userId))) + throw new EntityNotFoundException(String.format("Candidate comment with id %s does not belong to user with id %s", evidenceId, userId)); + return candidateEvidence; } } diff --git a/src/main/java/io/github/patternatlas/api/service/IssueService.java b/src/main/java/io/github/patternatlas/api/service/IssueService.java index f568e9d..b439773 100644 --- a/src/main/java/io/github/patternatlas/api/service/IssueService.java +++ b/src/main/java/io/github/patternatlas/api/service/IssueService.java @@ -1,41 +1,90 @@ package io.github.patternatlas.api.service; import java.util.List; -import java.util.UUID; +import io.github.patternatlas.api.entities.issue.IssueRating; +import io.github.patternatlas.api.entities.issue.author.IssueAuthor; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; import io.github.patternatlas.api.entities.issue.Issue; -import io.github.patternatlas.api.entities.issue.IssueComment; +import io.github.patternatlas.api.entities.issue.comment.IssueCommentRating; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidence; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidenceRating; +import io.github.patternatlas.api.rest.model.issue.IssueModelRequest; +import io.github.patternatlas.api.rest.model.shared.AuthorModelRequest; +import io.github.patternatlas.api.rest.model.shared.CommentModel; +import io.github.patternatlas.api.rest.model.shared.EvidenceModel; +import io.github.patternatlas.api.rest.model.shared.RatingModelRequest; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; + +import java.util.UUID; public interface IssueService { - /** - * CRUD - */ - Issue createIssue(Issue issue); + Issue saveIssue(Issue issue); + /** CRUD */ + @PreAuthorize(value = "hasGlobalPermission(@PC.ISSUE_CREATE)") + Issue createIssue(IssueModelRequest issueModelRequest, UUID userId); + @PostFilter("hasResourcePermission(filterObject.id, @PC.ISSUE_READ)") + List getAllIssues(); + + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_READ)") Issue getIssueById(UUID issueId); + @PostAuthorize(value = "hasResourcePermission(returnObject.id, @PC.ISSUE_READ)") Issue getIssueByURI(String uri); - List getAllIssues(); + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_EDIT)") + Issue updateIssue(UUID issueId, UUID userId, IssueModelRequest issueModelRequest); - Issue updateIssue(Issue issue); + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_VOTE)") + Issue updateIssueRating(UUID issueId, UUID userId, RatingModelRequest ratingModelRequest); + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_DELETE)") void deleteIssue(UUID issueId); - /** - * Voting - */ - Issue userRating(UUID issueId, UUID userId, String rating); + /** Author */ + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_EDIT)") + Issue saveIssueAuthor(UUID issueId, AuthorModelRequest authorModelRequest); + + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_EDIT)") + Issue deleteIssueAuthor(UUID issueId, UUID userId); - /** - * Comment - */ - Issue createComment(UUID issueId, UUID userId, IssueComment issueComment); + /** Comment */ + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_COMMENT)") + Issue createComment(UUID issueId, UUID userId, CommentModel commentModel); + @PostAuthorize(value = "hasResourcePermission(returnObject.issue.id, @PC.ISSUE_READ)") IssueComment getCommentById(UUID issueCommentId); - IssueComment updateComment(IssueComment issueComment); + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_EDIT)") + Issue updateComment(UUID issueId, UUID commentId, UUID userId, CommentModel commentModel); + + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_VOTE)") + Issue updateIssueCommentRating(UUID issueId, UUID commentId, UUID userId, RatingModelRequest ratingModelRequest); + + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_EDIT)") + Issue deleteComment(UUID issueId, UUID commentId, UUID userId); + + /** Evidence */ + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_EVIDENCE)") + Issue createEvidence(UUID issueId, UUID userId, EvidenceModel evidenceModel); + + @PostAuthorize(value = "hasResourcePermission(returnObject.issue.id, @PC.ISSUE_READ)") + IssueEvidence getEvidenceById(UUID issueEvidenceId); + + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_EDIT)") + Issue updateEvidence(UUID issueId, UUID evidenceId, UUID userId, EvidenceModel evidenceModel); + + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_VOTE)") + Issue updateIssueEvidenceRating(UUID issueId, UUID evidenceID, UUID userId, RatingModelRequest ratingModelRequest); + + @PreAuthorize(value = "hasResourcePermission(#issueId, @PC.ISSUE_EDIT)") + Issue deleteEvidence(UUID issueId, UUID evidenceId, UUID userId); + - Issue commentUserRating(UUID issueCommentId, UUID userId, String rating); -} +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/service/IssueServiceImpl.java b/src/main/java/io/github/patternatlas/api/service/IssueServiceImpl.java index e2071e7..3016bc6 100644 --- a/src/main/java/io/github/patternatlas/api/service/IssueServiceImpl.java +++ b/src/main/java/io/github/patternatlas/api/service/IssueServiceImpl.java @@ -1,54 +1,89 @@ package io.github.patternatlas.api.service; +import java.util.Arrays; import java.util.List; import java.util.UUID; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import java.util.stream.Collectors; import io.github.patternatlas.api.entities.issue.Issue; -import io.github.patternatlas.api.entities.issue.IssueComment; -import io.github.patternatlas.api.entities.issue.rating.IssueCommentRating; -import io.github.patternatlas.api.entities.issue.rating.IssueRating; +import io.github.patternatlas.api.entities.issue.IssueRating; +import io.github.patternatlas.api.entities.issue.author.IssueAuthor; +import io.github.patternatlas.api.entities.issue.comment.IssueComment; +import io.github.patternatlas.api.entities.issue.comment.IssueCommentRating; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidence; +import io.github.patternatlas.api.entities.issue.evidence.IssueEvidenceRating; +import io.github.patternatlas.api.entities.shared.AuthorConstant; import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.exception.CommentNotFoundException; -import io.github.patternatlas.api.exception.IssueNotFoundException; -import io.github.patternatlas.api.exception.NullCommentException; -import io.github.patternatlas.api.exception.NullIssueException; +import io.github.patternatlas.api.entities.user.role.Privilege; +import io.github.patternatlas.api.entities.user.role.PrivilegeConstant; +import io.github.patternatlas.api.entities.user.role.Role; +import io.github.patternatlas.api.entities.user.role.RoleConstant; +import io.github.patternatlas.api.repositories.IssueAuthorRepository; import io.github.patternatlas.api.repositories.IssueCommentRatingRepository; import io.github.patternatlas.api.repositories.IssueCommentRepository; +import io.github.patternatlas.api.repositories.IssueEvidenceRatingRepository; +import io.github.patternatlas.api.repositories.IssueEvidenceRepository; import io.github.patternatlas.api.repositories.IssueRatingRepository; import io.github.patternatlas.api.repositories.IssueRepository; -import io.github.patternatlas.api.util.RatingHelper; +import io.github.patternatlas.api.rest.model.issue.IssueModelRequest; +import io.github.patternatlas.api.rest.model.shared.AuthorModelRequest; +import io.github.patternatlas.api.rest.model.shared.CommentModel; +import io.github.patternatlas.api.rest.model.shared.EvidenceModel; +import io.github.patternatlas.api.rest.model.shared.RatingModelRequest; + +import javax.persistence.EntityExistsException; +import javax.persistence.EntityNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @Transactional public class IssueServiceImpl implements IssueService { - private final IssueRepository issueRepository; - private final IssueRatingRepository issueRatingRepository; - private final IssueCommentRepository issueCommentRepository; - private final IssueCommentRatingRepository issueCommentRatingRepository; - private final UserService userService; - private final RatingHelper ratingHelper; + private IssueRepository issueRepository; + private IssueRatingRepository issueRatingRepository; + private IssueAuthorRepository issueAuthorRepository; + private IssueCommentRepository issueCommentRepository; + private IssueCommentRatingRepository issueCommentRatingRepository; + private IssueEvidenceRepository issueEvidenceRepository; + private IssueEvidenceRatingRepository issueEvidenceRatingRepository; + private UserService userService; + private RoleService roleService; + private PrivilegeService privilegeService; Logger logger = LoggerFactory.getLogger(IssueServiceImpl.class); public IssueServiceImpl( IssueRepository issueRepository, IssueRatingRepository issueRatingRepository, + IssueAuthorRepository issueAuthorRepository, IssueCommentRepository issueCommentRepository, IssueCommentRatingRepository issueCommentRatingRepository, - UserService userService + IssueEvidenceRepository issueEvidenceRepository, + IssueEvidenceRatingRepository issueEvidenceRatingRepository, + UserService userService, + RoleService roleService, + PrivilegeService privilegeService ) { this.issueRepository = issueRepository; this.issueRatingRepository = issueRatingRepository; + this.issueAuthorRepository = issueAuthorRepository; this.issueCommentRepository = issueCommentRepository; this.issueCommentRatingRepository = issueCommentRatingRepository; + this.issueEvidenceRepository = issueEvidenceRepository; + this.issueEvidenceRatingRepository = issueEvidenceRatingRepository; this.userService = userService; - this.ratingHelper = new RatingHelper(); + this.roleService = roleService; + this.privilegeService = privilegeService; + } + + @Override + @Transactional + public Issue saveIssue(Issue issue) { + return this.issueRepository.save(issue); } /** @@ -56,170 +91,273 @@ public IssueServiceImpl( */ @Override @Transactional - public Issue createIssue(Issue issue) { - if (null == issue) { - throw new NullIssueException(); - } + public Issue createIssue(IssueModelRequest issueModelRequest, UUID userId) { + Issue issue = new Issue(issueModelRequest); + if (null == issue) + throw new RuntimeException("Issue to create is null"); + if (this.issueRepository.existsByName(issueModelRequest.getName())) + throw new EntityExistsException(String.format("Issue name %s already exist!", issueModelRequest.getName())); + if (this.issueRepository.existsByUri(issueModelRequest.getUri())) + throw new EntityExistsException(String.format("Issue uri %s already exist!", issueModelRequest.getUri())); Issue newIssue = this.issueRepository.save(issue); - logger.info(String.format("Create Issue %s: ", newIssue.toString())); - return newIssue; + UserEntity user = this.userService.getUserById(userId); + + Privilege readIssuePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.ISSUE_READ + '_' + newIssue.getId()); + Privilege updateIssuePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.ISSUE_EDIT + '_' + newIssue.getId()); + Privilege deleteIssuePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.ISSUE_DELETE + '_' + newIssue.getId()); + Privilege commentIssuePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.ISSUE_COMMENT + '_' + newIssue.getId()); + Privilege voteIssuePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.ISSUE_VOTE + '_' + newIssue.getId()); + Privilege evidenceIssuePrivilege = this.privilegeService.createPrivilege(PrivilegeConstant.ISSUE_EVIDENCE + '_' + newIssue.getId()); + Privilege toPatternCandidate = this.privilegeService.createPrivilege(PrivilegeConstant.ISSUE_TO_PATTERN_CANDIDATE + '_' + newIssue.getId()); + + Role helper = this.roleService.createRole(RoleConstant.HELPER + "_ISSUE_" + newIssue.getId(), Arrays.asList( + readIssuePrivilege, + commentIssuePrivilege, voteIssuePrivilege, evidenceIssuePrivilege + )); + Role maintainer = this.roleService.createRole(RoleConstant.MAINTAINER + "_ISSUE_" + newIssue.getId(), Arrays.asList( + readIssuePrivilege, updateIssuePrivilege, + commentIssuePrivilege, voteIssuePrivilege, evidenceIssuePrivilege + )); + Role owner = this.roleService.createRole(RoleConstant.OWNER + "_ISSUE_" + newIssue.getId(), Arrays.asList( + readIssuePrivilege, updateIssuePrivilege, deleteIssuePrivilege, toPatternCandidate, + commentIssuePrivilege, voteIssuePrivilege, evidenceIssuePrivilege + )); + + newIssue.getAuthors().add(new IssueAuthor(newIssue, user, AuthorConstant.OWNER)); + user.getRoles().add(owner); + return this.issueRepository.save(newIssue); } @Override @Transactional(readOnly = true) - public Issue getIssueById(UUID IssueId) { - return this.issueRepository.findById(IssueId) - .orElseThrow(() -> new IssueNotFoundException(IssueId)); + public List getAllIssues() { + return this.issueRepository.findAll(); } @Override @Transactional(readOnly = true) - public Issue getIssueByURI(String uri) { - return this.issueRepository.findByUri(uri) - .orElseThrow(() -> new IssueNotFoundException(String.format("Issue with URI %s not found!", uri))); + public Issue getIssueById(UUID issueId) { + return this.issueRepository.findById(issueId) + .orElseThrow(() -> new ResourceNotFoundException(String.format("Issue with ID %s not found!", issueId))); } @Override @Transactional(readOnly = true) - public List getAllIssues() { - return this.issueRepository.findAll(); + public Issue getIssueByURI(String uri) { + return this.issueRepository.findByUri(uri) + .orElseThrow(() -> new ResourceNotFoundException(String.format("Issue with URI %s not found!", uri))); } @Override @Transactional - public Issue updateIssue(Issue issue) { - if (null == issue) { - throw new NullIssueException(); - } - if (!this.issueRepository.existsById(issue.getId())) { - throw new IssueNotFoundException(String.format("Issue %s not found", issue.getId())); + public Issue updateIssue(UUID issueId, UUID userId, IssueModelRequest issueModelRequest) { + if (issueModelRequest == null) { + throw new RuntimeException("Issue to update is null!"); } + Issue issue = this.getIssueById(issueId); - logger.info(String.format("Update Issue: %s", issue.toString())); + // UPDATE issue fields + if (this.issueRepository.existsByUri(issueModelRequest.getUri())) { + Issue issueUri = this.getIssueByURI(issueModelRequest.getUri()); + // NOT uri & name change + if (!issueUri.getId().equals(issue.getId())) { + throw new EntityExistsException(String.format("Issue uri %s already exist!", issueModelRequest.getUri())); + } + } + issue.updateIssue(issueModelRequest); return this.issueRepository.save(issue); } @Override @Transactional - public void deleteIssue(UUID IssueId) { - Issue issue = this.getIssueById(IssueId); - if (null == issue) { - throw new NullIssueException(); + public Issue updateIssueRating(UUID issueId, UUID userId, RatingModelRequest ratingModelRequest) { + Issue issue = this.getIssueById(issueId); + UserEntity user = this.userService.getUserById(userId); + IssueRating issueRating = new IssueRating(issue, user, ratingModelRequest.getRating()); + if (this.issueRatingRepository.existsByIdAndRating(issueRating.getId(), issueRating.getRating())) { + issueRating.setRating(0); } + issueRating = this.issueRatingRepository.save(issueRating); + return issueRating.getIssue(); + } - this.issueRepository.deleteById(IssueId); + @Override + @Transactional + public void deleteIssue(UUID issueId) { + this.roleService.deleteAllRolesByResourceId(issueId); + this.privilegeService.deleteAllPrivilegesByResourceId(issueId); + this.issueRepository.deleteById(issueId); } /** - * Voting Issue - * - * @param rating up, down - * @apiNote If user already up- or downvoted and does so again it neutralises vote. + * Author */ @Override @Transactional - public Issue userRating(UUID IssueId, UUID userId, String rating) { - Issue issue = this.getIssueById(IssueId); - UserEntity user = this.userService.getUserById(userId); + public Issue saveIssueAuthor(UUID issueId, AuthorModelRequest authorModelRequest) { + Issue issue = this.getIssueById(issueId); + UserEntity user = userService.getUserById(authorModelRequest.getUserId()); + IssueAuthor issueAuthor = new IssueAuthor(issue, user, authorModelRequest.getAuthorRole()); + issueAuthor = this.issueAuthorRepository.save(issueAuthor); - logger.info(String.format("User %s updates rating for Issue %s", user.getId(), issue.getId())); + // Save roles associated with the author + // Get requested role + List authorRoles = this.roleService.findAllFromEntityForAuthorRole(issue.getId(), + authorModelRequest.getAuthorRole()); + user.getRoles().addAll(authorRoles); + this.userService.saveUser(user); - IssueRating issueRating = new IssueRating(issue, user); - if (this.issueRatingRepository.existsById(issueRating.getId())) { - issueRating = this.issueRatingRepository.findByIssueAndUser(issue, user); - logger.info(String.format("Rating for user %s exists with rating %s", user.getId(), issueRating.getRating())); - } else { - logger.info(String.format("Rating for user %s does not exist", user.getId())); - } + return issueAuthor.getIssue(); + } - int newRating = this.ratingHelper.updateRating(rating, issueRating.getRating(), user); - if (newRating == -2) { - return null; - } else { - issueRating.setRating(newRating); - } + @Override + @Transactional + public Issue deleteIssueAuthor(UUID issueId, UUID userId) { + Issue issue = this.getIssueById(issueId); + UserEntity user = this.userService.getUserById(userId); + IssueAuthor issueAuthor = new IssueAuthor(issue, user); + if (this.issueAuthorRepository.existsById(issueAuthor.getId())) { + List roles = this.userService.getAllRolesFromEntity(issueId) // find all roles for issue + .stream().filter((role) -> role.getUsers().contains(user)) // filter for roles for current user + .collect(Collectors.toList()); - issue.getUserRating().add(issueRating); - int updateRating = issue.getUserRating().stream().mapToInt(IssueRating::getRating).sum(); - issue.setRating(updateRating); + user.getRoles().removeAll(roles); // delete only those - return this.updateIssue(issue); + this.userService.saveUser(user); + this.issueAuthorRepository.deleteById(issueAuthor.getId()); + } + return this.getIssueById(issueId); } /** - * Comment Issue + * Comment */ @Override @Transactional - public Issue createComment(UUID issueId, UUID userId, IssueComment issueComment) { + public Issue createComment(UUID issueId, UUID userId, CommentModel commentModel) { Issue issue = this.getIssueById(issueId); UserEntity user = this.userService.getUserById(userId); - IssueComment comment = new IssueComment(issueComment.getText()); - issue.addComment(comment, user); + IssueComment comment = this.issueCommentRepository.save(new IssueComment(commentModel.getText(), issue, user)); + return comment.getIssue(); + } - return this.updateIssue(issue); + @Override + @Transactional(readOnly = true) + public IssueComment getCommentById(UUID commentId) { + return this.issueCommentRepository.findById(commentId) + .orElseThrow(() -> new ResourceNotFoundException(String.format("Issue comment with ID %s not found!", commentId))); } @Override @Transactional - public IssueComment updateComment(IssueComment issueComment) { - if (null == issueComment) { - throw new NullCommentException(); - } - if (!this.issueCommentRepository.existsById(issueComment.getId())) { - throw new CommentNotFoundException(String.format("Comment %s for issue %s not found", issueComment.getId(), issueComment.getIssue().getId())); - } + public Issue updateComment(UUID issueId, UUID commentId, UUID userId, CommentModel commentModel) { + if (commentModel == null) + throw new RuntimeException("Issue comment to update is null!"); + IssueComment comment = this.existsIssueComment(issueId, commentId, userId); + comment.updateComment(commentModel.getText()); + comment = this.issueCommentRepository.save(comment); + return comment.getIssue(); + } - logger.info(String.format("Update issue comment: %s", issueComment.toString())); - return this.issueCommentRepository.save(issueComment); + @Override + @Transactional + public Issue updateIssueCommentRating(UUID issueId, UUID commentId, UUID userId, RatingModelRequest ratingModelRequest) { + IssueComment issueComment = this.getCommentById(commentId); + UserEntity user = this.userService.getUserById(userId); + IssueCommentRating issueCommentRating = new IssueCommentRating(issueComment, user, ratingModelRequest.getRating()); + if (this.issueCommentRatingRepository.existsByIdAndRating(issueCommentRating.getId(), issueCommentRating.getRating())) { + issueCommentRating.setRating(0); + } + issueCommentRating = this.issueCommentRatingRepository.save(issueCommentRating); + return issueCommentRating.getIssueComment().getIssue(); } @Override - @Transactional(readOnly = true) - public IssueComment getCommentById(UUID commentId) { - return this.issueCommentRepository.findById(commentId) - .orElseThrow(() -> new CommentNotFoundException(commentId)); + @Transactional + public Issue deleteComment(UUID issueId, UUID commentId, UUID userId) { + this.existsIssueComment(issueId, commentId, userId); + this.issueCommentRepository.deleteById(commentId); + return this.getIssueById(issueId); + } + + // Check if Issue comment exists + private IssueComment existsIssueComment(UUID issueId, UUID commentId, UUID userId) { + IssueComment issueComment = this.getCommentById(commentId); + // CORRECT Issue + if (!issueComment.getIssue().equals(this.getIssueById(issueId))) + throw new EntityNotFoundException(String.format("Issue comment with id %s does not belong to issue with id %s", commentId, issueId)); + // CORRECT user + if (!issueComment.getUser().equals(this.userService.getUserById(userId))) + throw new EntityNotFoundException(String.format("Issue comment with id %s does not belong to user with id %s", commentId, userId)); + return issueComment; } /** - * Voting IssueComment - * - * @param rating up, down - * @apiNote If user already up- or downvoted and does so again it neutralises vote. + * Evidence */ @Override @Transactional - public Issue commentUserRating(UUID issueCommentId, UUID userId, String rating) { - IssueComment issueComment = this.getCommentById(issueCommentId); + public Issue createEvidence(UUID issueId, UUID userId, EvidenceModel evidenceModel) { + Issue issue = this.getIssueById(issueId); UserEntity user = this.userService.getUserById(userId); - logger.info(String.format("User %s updates rating for Comment %s", user.getId(), issueComment.getId())); + IssueEvidence issueEvidence = new IssueEvidence(evidenceModel.getTitle(), evidenceModel.getContext(), evidenceModel.getType(), evidenceModel.getSupporting(), evidenceModel.getSource(), issue, user); + issueEvidence = this.issueEvidenceRepository.save(issueEvidence); + return issueEvidence.getIssue(); + } - IssueCommentRating issueCommentRating = new IssueCommentRating(issueComment, user); + @Override + @Transactional(readOnly = true) + public IssueEvidence getEvidenceById(UUID issueEvidenceId) { + return this.issueEvidenceRepository.findById(issueEvidenceId) + .orElseThrow(() -> new ResourceNotFoundException(String.format("Issue comment with ID %s not found!", issueEvidenceId))); + } - if (this.issueCommentRatingRepository.existsById(issueCommentRating.getId())) { - issueCommentRating = this.issueCommentRatingRepository.findByIssueCommentAndUser(issueComment, user); - logger.info(String.format("Rating for user %s exists with rating %s", user.getId(), issueCommentRating.getRating())); - } else { - logger.info(String.format("Rating for user %s does not exist", user.getId())); - } + @Override + @Transactional + public Issue updateEvidence(UUID issueId, UUID evidenceId, UUID userId, EvidenceModel evidenceModel) { + if (evidenceModel == null) + throw new RuntimeException("Issue comment to update is null!"); + IssueEvidence issueEvidence = this.existsIssueEvidence(issueId, evidenceId, userId); + // UPDATE issue evidence + issueEvidence.updateEvidence(evidenceModel.getTitle(), evidenceModel.getContext(), evidenceModel.getType(), evidenceModel.getSupporting(), evidenceModel.getSource()); + issueEvidence = this.issueEvidenceRepository.save(issueEvidence); + return issueEvidence.getIssue(); + } - int newRating = this.ratingHelper.updateRating(rating, issueCommentRating.getRating(), user); - if (newRating == -2) { - return null; - } else { - issueCommentRating.setRating(newRating); + @Override + @Transactional + public Issue updateIssueEvidenceRating(UUID issueId, UUID evidenceID, UUID userId, RatingModelRequest ratingModelRequest) { + Issue issue = this.getIssueById(issueId); + IssueEvidence issueEvidence = this.getEvidenceById(evidenceID); + UserEntity user = this.userService.getUserById(userId); + IssueEvidenceRating issueEvidenceRating = new IssueEvidenceRating(issueEvidence, user, ratingModelRequest.getRating()); + if (this.issueEvidenceRatingRepository.existsByIdAndRating(issueEvidenceRating.getId(), issueEvidenceRating.getRating())) { + issueEvidenceRating.setRating(0); } + issueEvidenceRating = this.issueEvidenceRatingRepository.save(issueEvidenceRating); + return issueEvidenceRating.getIssueEvidence().getIssue(); + } - issueComment.getUserRating().add(issueCommentRating); - int updateRating = issueComment.getUserRating().stream().mapToInt(IssueCommentRating::getRating).sum(); - issueComment.setRating((updateRating)); - logger.info(String.format("New rating for comment is: %d", updateRating)); - this.updateComment(issueComment); + @Override + @Transactional + public Issue deleteEvidence(UUID issueId, UUID evidenceId, UUID userId) { + this.existsIssueEvidence(issueId, evidenceId, userId); + this.issueEvidenceRepository.deleteById(evidenceId); + return this.getIssueById(issueId); + } - return this.getIssueById(issueComment.getIssue().getId()); + private IssueEvidence existsIssueEvidence(UUID issueId, UUID commentId, UUID userId) { + IssueEvidence issueEvidence = this.getEvidenceById(commentId); + // CORRECT Issue + if (!issueEvidence.getIssue().equals(this.getIssueById(issueId))) + throw new EntityNotFoundException(String.format("Issue comment with id %s does not belong to issue with id %s", commentId, issueId)); + // CORRECT user + if (!issueEvidence.getUser().equals(this.userService.getUserById(userId))) + throw new EntityNotFoundException(String.format("Issue comment with id %s does not belong to user with id %s", commentId, userId)); + return issueEvidence; } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/service/PatternLanguageService.java b/src/main/java/io/github/patternatlas/api/service/PatternLanguageService.java index c666fee..05d9293 100644 --- a/src/main/java/io/github/patternatlas/api/service/PatternLanguageService.java +++ b/src/main/java/io/github/patternatlas/api/service/PatternLanguageService.java @@ -3,7 +3,9 @@ import java.util.List; import java.util.UUID; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; import io.github.patternatlas.api.entities.DirectedEdge; import io.github.patternatlas.api.entities.Pattern; @@ -16,80 +18,85 @@ public interface PatternLanguageService { - @Transactional + @PreAuthorize(value = "hasGlobalPermission(@PC.PATTERN_LANGUAGE_CREATE)") PatternLanguage createPatternLanguage(PatternLanguage patternLanguage); - @Transactional(readOnly = true) + @PostFilter("hasResourcePermission(filterObject.id, @PC.PATTERN_LANGUAGE_READ)") List getPatternLanguages(); - @Transactional(readOnly = true) + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_READ)") PatternLanguage getPatternLanguageById(UUID patternLanguageId); - @Transactional(readOnly = true) + @PostAuthorize(value = "hasResourcePermission(returnObject.id, @PC.PATTERN_LANGUAGE_READ)") PatternLanguage getPatternLanguageByUri(String uri); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguage.id, @PC.PATTERN_LANGUAGE_EDIT)") PatternLanguage updatePatternLanguage(PatternLanguage patternLanguage); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_DELETE)") void deletePatternLanguage(UUID patternLanguageId); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT) && hasGlobalPermission(@PC.APPROVED_PATTERN_CREATE)") Pattern createPatternAndAddToPatternLanguage(UUID patternLanguageId, Pattern pattern); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_READ)") + @PostFilter("hasResourcePermission(filterObject.id, @PC.APPROVED_PATTERN_READ)") List getPatternsOfPatternLanguage(UUID patternLanguageId); - @Transactional(readOnly = true) + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_READ) && hasResourcePermission(#patternId, @PC.APPROVED_PATTERN_READ)") Pattern getPatternOfPatternLanguageById(UUID patternLanguageId, UUID patternId); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT) && hasResourcePermission(#patternId, @PC.APPROVED_PATTERN_DELETE)") void deletePatternOfPatternLanguage(UUID patternLanguageId, UUID patternId); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") PatternSchema createPatternSchemaAndAddToPatternLanguage(UUID patternLanguageId, PatternSchema patternSchema); - @Transactional(readOnly = true) + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_READ)") PatternSchema getPatternSchemaByPatternLanguageId(UUID patternLanguageId); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") PatternSchema updatePatternSchemaOfPatternLanguage(UUID patternLanguageId, PatternSchema patternSchema); - @Transactional(readOnly = true) + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_READ)") Object getGraphOfPatternLanguage(UUID patternLanguageId); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") Object createGraphOfPatternLanguage(UUID patternLanguageId, Object graph); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") Object updateGraphOfPatternLanguage(UUID patternLanguageId, Object graph); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") void deleteGraphOfPatternLanguage(UUID patternLanguageId); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") DirectedEdge createDirectedEdgeAndAddToPatternLanguage(UUID patternLanguageId, CreateDirectedEdgeRequest createDirectedEdgeRequest); + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_READ)") List getDirectedEdgesOfPatternLanguage(UUID patternLanguageId); + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_READ)") DirectedEdge getDirectedEdgeOfPatternLanguageById(UUID patternLanguageId, UUID directedEdgeId); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") DirectedEdge updateDirectedEdgeOfPatternLanguage(UUID patternLanguageId, DirectedEdge directedEdge); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") void removeDirectedEdgeFromPatternLanguage(UUID patternLanguageId, UUID directedEdgeId); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") UndirectedEdge createUndirectedEdgeAndAddToPatternLanguage(UUID patternLanguageId, CreateUndirectedEdgeRequest createUndirectedEdgeRequest); - @Transactional(readOnly = true) + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_READ)") List getUndirectedEdgesOfPatternLanguage(UUID patternLanguageId); + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_READ)") UndirectedEdge getUndirectedEdgeOfPatternLanguageById(UUID patternLanguageId, UUID undirectedEdgeId) throws UndirectedEdgeNotFoundException; + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") UndirectedEdge updateUndirectedEdgeOfPatternLanguage(UUID patternLanguageId, UndirectedEdge undirectedEdge); - @Transactional + @PreAuthorize(value = "hasResourcePermission(#patternLanguageId, @PC.PATTERN_LANGUAGE_EDIT)") void removeUndirectedEdgeFromPatternLanguage(UUID patternLanguageId, UUID undirectedEdgeId); } diff --git a/src/main/java/io/github/patternatlas/api/service/PatternLanguageServiceImpl.java b/src/main/java/io/github/patternatlas/api/service/PatternLanguageServiceImpl.java index 425c2a6..3f2b799 100644 --- a/src/main/java/io/github/patternatlas/api/service/PatternLanguageServiceImpl.java +++ b/src/main/java/io/github/patternatlas/api/service/PatternLanguageServiceImpl.java @@ -142,7 +142,7 @@ public void deletePatternLanguage(UUID patternLanguageId) { } } - this.patternSchemaService.deletePatternSchemaById(patternLanguageId); + this.patternSchemaService.deletePatternSchema(patternLanguage.getPatternSchema()); this.patternLanguageRepository.delete(patternLanguage); } @@ -232,7 +232,7 @@ public void deletePatternOfPatternLanguage(UUID patternLanguageId, UUID patternI } // 2. Remove Pattern from Views it is included - pattern.setPatternViews(null); + pattern.getPatternViews().clear(); pattern = this.patternService.updatePattern(pattern); // 3. Remove pattern diff --git a/src/main/java/io/github/patternatlas/api/service/PatternSchemaService.java b/src/main/java/io/github/patternatlas/api/service/PatternSchemaService.java index 1567008..592e0d3 100644 --- a/src/main/java/io/github/patternatlas/api/service/PatternSchemaService.java +++ b/src/main/java/io/github/patternatlas/api/service/PatternSchemaService.java @@ -3,14 +3,20 @@ import java.util.UUID; import io.github.patternatlas.api.entities.PatternSchema; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PreAuthorize; public interface PatternSchemaService { + @PreAuthorize(value = "hasResourcePermission(#patternSchema.patternLanguage.id, @PC.PATTERN_LANGUAGE_EDIT)") PatternSchema createPatternSchema(PatternSchema patternSchema); + @PreAuthorize(value = "hasResourcePermission(#patternSchema.patternLanguage.id, @PC.PATTERN_LANGUAGE_EDIT)") PatternSchema updatePatternSchema(PatternSchema patternSchema); + @PostAuthorize(value = "hasResourcePermission(#returnObject.patternLanguage.id, @PC.PATTERN_LANGUAGE_READ)") PatternSchema getPatternSchemaById(UUID id); - void deletePatternSchemaById(UUID id); + @PreAuthorize(value = "hasResourcePermission(#schema.patternLanguage.id, @PC.PATTERN_LANGUAGE_EDIT)") + void deletePatternSchema(PatternSchema schema); } diff --git a/src/main/java/io/github/patternatlas/api/service/PatternSchemaServiceImpl.java b/src/main/java/io/github/patternatlas/api/service/PatternSchemaServiceImpl.java index 2b6e20c..0c9c943 100644 --- a/src/main/java/io/github/patternatlas/api/service/PatternSchemaServiceImpl.java +++ b/src/main/java/io/github/patternatlas/api/service/PatternSchemaServiceImpl.java @@ -74,7 +74,7 @@ public PatternSchema getPatternSchemaById(UUID id) { @Override @Transactional - public void deletePatternSchemaById(UUID id) { - this.patternSchemaRepository.deleteById(id); + public void deletePatternSchema(PatternSchema schema) { + this.patternSchemaRepository.deleteById(schema.getId()); } } diff --git a/src/main/java/io/github/patternatlas/api/service/PatternService.java b/src/main/java/io/github/patternatlas/api/service/PatternService.java index 078c05d..257e9ce 100644 --- a/src/main/java/io/github/patternatlas/api/service/PatternService.java +++ b/src/main/java/io/github/patternatlas/api/service/PatternService.java @@ -5,16 +5,23 @@ import io.github.patternatlas.api.entities.Pattern; import io.github.patternatlas.api.validator.PatternContentConstraint; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PreAuthorize; public interface PatternService { + @PreAuthorize(value = "hasGlobalPermission(@PC.APPROVED_PATTERN_CREATE)") Pattern createPattern(@Valid @PatternContentConstraint Pattern pattern); + @PreAuthorize(value = "hasResourcePermission(#pattern.id, @PC.APPROVED_PATTERN_EDIT)") Pattern updatePattern(@Valid @PatternContentConstraint Pattern pattern); + @PreAuthorize(value = "hasResourcePermission(#pattern.id, @PC.APPROVED_PATTERN_DELETE)") void deletePattern(Pattern pattern); + @PostAuthorize(value = "hasResourcePermission(returnObject.id, @PC.APPROVED_PATTERN_READ)") Pattern getPatternById(UUID patternId); + @PostAuthorize(value = "hasResourcePermission(returnObject.id, @PC.APPROVED_PATTERN_READ)") Pattern getPatternByUri(String uri); } diff --git a/src/main/java/io/github/patternatlas/api/service/PatternServiceImpl.java b/src/main/java/io/github/patternatlas/api/service/PatternServiceImpl.java index ab051df..e440509 100644 --- a/src/main/java/io/github/patternatlas/api/service/PatternServiceImpl.java +++ b/src/main/java/io/github/patternatlas/api/service/PatternServiceImpl.java @@ -3,6 +3,8 @@ import java.util.UUID; import javax.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -21,6 +23,8 @@ public class PatternServiceImpl implements PatternService { private final PatternRepository patternRepository; + Logger logger = LoggerFactory.getLogger(PatternServiceImpl.class); + public PatternServiceImpl(PatternRepository patternRepository) { this.patternRepository = patternRepository; } diff --git a/src/main/java/io/github/patternatlas/api/service/PatternViewService.java b/src/main/java/io/github/patternatlas/api/service/PatternViewService.java index 8804cf8..94f5426 100644 --- a/src/main/java/io/github/patternatlas/api/service/PatternViewService.java +++ b/src/main/java/io/github/patternatlas/api/service/PatternViewService.java @@ -3,7 +3,9 @@ import java.util.List; import java.util.UUID; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; +import org.springframework.security.access.prepost.PreAuthorize; import io.github.patternatlas.api.entities.DirectedEdge; import io.github.patternatlas.api.entities.Pattern; @@ -16,59 +18,82 @@ public interface PatternViewService { + @PreAuthorize(value = "hasGlobalPermission(@PC.PATTERN_VIEW_CREATE)") PatternView createPatternView(PatternView patternView); + @PostFilter("hasResourcePermission(filterObject.id, @PC.PATTERN_VIEW_READ)") List getAllPatternViews(); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_READ)") PatternView getPatternViewById(UUID patternViewId); + @PostAuthorize(value = "hasResourcePermission(returnObject.id, @PC.PATTERN_VIEW_READ)") PatternView getPatternViewByUri(String uri); + @PreAuthorize(value = "hasResourcePermission(#patternView.id, @PC.PATTERN_VIEW_EDIT)") PatternView updatePatternView(PatternView patternView); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_DELETE)") void deletePatternView(UUID patternViewId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") void addPatternToPatternView(UUID patternViewId, UUID patternId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_READ)") + @PostFilter("hasResourcePermission(filterObject.id, @PC.APPROVED_PATTERN_READ)") List getPatternsOfPatternView(UUID patternViewId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_READ) && hasResourcePermission(#patternId, @PC.APPROVED_PATTERN_READ)") Pattern getPatternOfPatternViewById(UUID patternViewId, UUID patternId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") void removePatternFromPatternView(UUID patternViewId, UUID patternId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") void addDirectedEdgeToPatternView(UUID patternViewId, UUID directedEdgeId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") DirectedEdge createDirectedEdgeAndAddToPatternView(UUID patternViewId, AddDirectedEdgeToViewRequest request); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_READ)") List getDirectedEdgesByPatternViewId(UUID patternViewId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_READ)") DirectedEdge getDirectedEdgeOfPatternViewById(UUID patternViewId, UUID directedEdgeId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") DirectedEdge updateDirectedEdgeOfPatternView(UUID patternViewId, UpdateDirectedEdgeRequest request); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") void removeDirectedEdgeFromPatternView(UUID patternViewId, UUID directedEdgeId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") void addUndirectedEdgeToPatternView(UUID patternViewId, UUID undirectedEdgeId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") UndirectedEdge createUndirectedEdgeAndAddToPatternView(UUID patternViewId, AddUndirectedEdgeToViewRequest request); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_READ)") List getUndirectedEdgesByPatternViewId(UUID patternViewId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_READ)") UndirectedEdge getUndirectedEdgeOfPatternViewById(UUID patternViewId, UUID undirectedEdgeId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") UndirectedEdge updateUndirectedEdgeOfPatternView(UUID patternViewId, UpdateUndirectedEdgeRequest request); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") void removeUndirectedEdgeFromPatternView(UUID patternViewId, UUID undirectedEdgeId); - @Transactional(readOnly = true) - Object getGraphOfPatternView(UUID patternLanguageId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_READ)") + Object getGraphOfPatternView(UUID patternViewId); - @Transactional - Object createGraphOfPatternView(UUID patternLanguageId, Object graph); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") + Object createGraphOfPatternView(UUID patternViewId, Object graph); - @Transactional - Object updateGraphOfPatternView(UUID patternLanguageId, Object graph); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") + Object updateGraphOfPatternView(UUID patternViewId, Object graph); - @Transactional - void deleteGraphOfPatternView(UUID patternLanguageId); + @PreAuthorize(value = "hasResourcePermission(#patternViewId, @PC.PATTERN_VIEW_EDIT)") + void deleteGraphOfPatternView(UUID patternViewId); } diff --git a/src/main/java/io/github/patternatlas/api/service/PrivilegeService.java b/src/main/java/io/github/patternatlas/api/service/PrivilegeService.java new file mode 100644 index 0000000..a8bbbc6 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/service/PrivilegeService.java @@ -0,0 +1,13 @@ +package io.github.patternatlas.api.service; + +import io.github.patternatlas.api.entities.user.role.Privilege; + +import java.util.List; +import java.util.UUID; + +public interface PrivilegeService { + /** CRUD */ + Privilege createPrivilege(String name); + + void deleteAllPrivilegesByResourceId(UUID entityId); +} diff --git a/src/main/java/io/github/patternatlas/api/service/PrivilegeServiceImpl.java b/src/main/java/io/github/patternatlas/api/service/PrivilegeServiceImpl.java new file mode 100644 index 0000000..7a576cf --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/service/PrivilegeServiceImpl.java @@ -0,0 +1,44 @@ +package io.github.patternatlas.api.service; + +import java.util.UUID; + +import io.github.patternatlas.api.entities.user.role.Privilege; +import io.github.patternatlas.api.repositories.PrivilegeRepository; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +public class PrivilegeServiceImpl implements PrivilegeService { + + private PrivilegeRepository privilegeRepository; + + Logger logger = LoggerFactory.getLogger(PrivilegeServiceImpl.class); + + public PrivilegeServiceImpl( + PrivilegeRepository privilegeRepository + ) { + this.privilegeRepository = privilegeRepository; + } + + /** + * CRUD Privilege + */ + @Override + @Transactional + public Privilege createPrivilege(String name) { + Privilege privilege = new Privilege(name); + + return this.privilegeRepository.save(privilege); + } + + @Override + @Transactional + public void deleteAllPrivilegesByResourceId(UUID entityId) { + this.privilegeRepository.deleteAllFromEntity(entityId); + } + +} diff --git a/src/main/java/io/github/patternatlas/api/service/RoleService.java b/src/main/java/io/github/patternatlas/api/service/RoleService.java new file mode 100644 index 0000000..243405a --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/service/RoleService.java @@ -0,0 +1,26 @@ +package io.github.patternatlas.api.service; + +import io.github.patternatlas.api.entities.user.role.Role; +import io.github.patternatlas.api.entities.user.role.Privilege; + +import java.util.List; +import java.util.UUID; +import java.util.Collection; + +public interface RoleService { + /** CRUD */ + Role createRole(String name, Collection privileges); + + void deleteAllRolesByResourceId(UUID entityId); + + List findAllFromEntityForAuthorRole(UUID entityId, String authorRole); + + /** + * Checks if a user has one of the supplied privileges, without querying all + * available roles and privileges + * @param roleId + * @param privileges + * @return true if one of the privileges is present for the user + */ + boolean hasAnyPrivilege(UUID roleId, String ... privileges); +} diff --git a/src/main/java/io/github/patternatlas/api/service/RoleServiceImpl.java b/src/main/java/io/github/patternatlas/api/service/RoleServiceImpl.java new file mode 100644 index 0000000..915d441 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/service/RoleServiceImpl.java @@ -0,0 +1,65 @@ +package io.github.patternatlas.api.service; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +import io.github.patternatlas.api.entities.user.role.Privilege; +import io.github.patternatlas.api.entities.user.role.Role; +import io.github.patternatlas.api.repositories.RoleRepository; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +public class RoleServiceImpl implements RoleService { + + private RoleRepository roleRepository; + + Logger logger = LoggerFactory.getLogger(RoleServiceImpl.class); + + public RoleServiceImpl( + RoleRepository roleRepository + ) { + this.roleRepository = roleRepository; + } + + /** + * CRUD Role + */ + @Override + @Transactional + public Role createRole(String name, Collection privileges) { + Role role = roleRepository.findByName(name); + if (role == null) { + role = new Role(name); + role.setPrivileges(privileges); + roleRepository.save(role); + } + return role; + } + + @Override + @Transactional + public void deleteAllRolesByResourceId(UUID entityId) { + this.roleRepository.deleteAllFromEntity(entityId); + } + + @Override + public List findAllFromEntityForAuthorRole(UUID entityId, String authorRole) { + return this.roleRepository.findAllFromEntityForAuthorRole(entityId, authorRole); + } + + @Override + public boolean hasAnyPrivilege(UUID roleId, String... privileges) { + for (String permission : privileges) { + if (this.roleRepository.existsPrivilegeForRole(permission, roleId)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/io/github/patternatlas/api/service/UserAuthService.java b/src/main/java/io/github/patternatlas/api/service/UserAuthService.java new file mode 100644 index 0000000..4d1be48 --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/service/UserAuthService.java @@ -0,0 +1,25 @@ +package io.github.patternatlas.api.service; + +import io.github.patternatlas.api.entities.user.UserEntity; +import io.github.patternatlas.api.rest.model.user.UserModelRequest; + +import java.util.UUID; + +/** + * Service for manipulating users based on incomping JW-Tokens. + * Not to be used for REST-Access. + * Used to query user information to handle authentication. + */ +public interface UserAuthService { + UserEntity createInitialMember(UserModelRequest userModelRequest); + + UserEntity createInitialAdmin(UserModelRequest userModelRequest); + + boolean userExists(UUID userId); + + /** + * Checks if there are users in the db already + * @return + */ + boolean hasUsers(); +} diff --git a/src/main/java/io/github/patternatlas/api/service/UserService.java b/src/main/java/io/github/patternatlas/api/service/UserService.java index 558a246..adac6c8 100644 --- a/src/main/java/io/github/patternatlas/api/service/UserService.java +++ b/src/main/java/io/github/patternatlas/api/service/UserService.java @@ -1,19 +1,64 @@ package io.github.patternatlas.api.service; +import io.github.patternatlas.api.entities.user.role.Privilege; +import io.github.patternatlas.api.entities.user.role.Role; +import io.github.patternatlas.api.entities.user.UserEntity; +import io.github.patternatlas.api.rest.model.user.RoleModelRequest; +import io.github.patternatlas.api.rest.model.user.UserModelRequest; +import org.springframework.security.access.prepost.PreAuthorize; + import java.util.List; import java.util.UUID; -import io.github.patternatlas.api.entities.user.UserEntity; - public interface UserService { + UserEntity saveUser(UserEntity user); - UserEntity createUser(UserEntity user); - UserEntity getUserById(UUID UserId); + @PreAuthorize(value = "hasGlobalPermission(@PC.USER_CREATE)") + UserEntity createUser(UserModelRequest userModelRequest); List getAllUsers(); - UserEntity updateUser(UserEntity user); + UserEntity getUserById(UUID UserId); + + @PreAuthorize(value = "hasGlobalPermission(@PC.USER_EDIT_ALL)") + UserEntity updateUser(UUID userId, UserModelRequest userModelRequest); + + @PreAuthorize(value = "hasGlobalPermission(@PC.USER_DELETE_ALL)" + + "or (hasGlobalPermission(@PC.USER_DELETE) and #userId.equals(loggedInUUID()))") + void deleteUser(UUID userId); + + /** + * Updates the platform wide roles for a given user (does not change other roles even when they are supplied + * in the request). + * @param userId + * @param userModelRequest + * @return + */ + @PreAuthorize(value = "hasGlobalPermission(@PC.USER_EDIT_ALL)") + UserEntity updatePlatformRole(UUID userId, UserModelRequest userModelRequest); + + /** + * Checks if a user has one of the supplied privileges, without querying all + * available roles and privileges + * @param userId + * @param privileges + * @return true if one of the privileges is present for the user + */ + boolean hasAnyPrivilege(UUID userId, String ... privileges); + + /** Role */ + List getAllRoles(); + List getAllPlatformRoles(); + List getAllAuthorRoles(); + List getAllRolesFromEntity(UUID entityId); + List getAllPlatformPrivileges(); + List getAllDefaultAuthorPrivileges(); + List getAllPrivilegesFromEntity(UUID entityId); + + @PreAuthorize(value = "hasGlobalPermission(@PC.USER_EDIT_ALL)") + Role updateRole(UUID roleId, UUID privilegeId, RoleModelRequest roleModelRequest); - void deleteUser(UUID UserId); -} + @PreAuthorize(value = "hasGlobalPermission(@PC.USER_EDIT_ALL)") + void updateAllResourceSpecificRoles(UUID authorRoleId, UUID defaultAuthorPrivilegeId, RoleModelRequest roleModelRequest); +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/service/UserServiceImpl.java b/src/main/java/io/github/patternatlas/api/service/UserServiceImpl.java index 76676c0..d2fe5b0 100644 --- a/src/main/java/io/github/patternatlas/api/service/UserServiceImpl.java +++ b/src/main/java/io/github/patternatlas/api/service/UserServiceImpl.java @@ -1,76 +1,298 @@ package io.github.patternatlas.api.service; -import java.util.List; -import java.util.UUID; +import io.github.patternatlas.api.entities.user.role.RoleConstant; +import io.github.patternatlas.api.entities.user.role.Privilege; +import io.github.patternatlas.api.entities.user.role.Role; +import io.github.patternatlas.api.entities.user.UserEntity; +import io.github.patternatlas.api.repositories.PrivilegeRepository; +import io.github.patternatlas.api.repositories.RoleRepository; +import io.github.patternatlas.api.repositories.UserRepository; +import io.github.patternatlas.api.rest.model.user.RoleModel; +import io.github.patternatlas.api.rest.model.user.RoleModelRequest; +import io.github.patternatlas.api.rest.model.user.UserModelRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import io.github.patternatlas.api.entities.user.UserEntity; -import io.github.patternatlas.api.exception.NullUserException; -import io.github.patternatlas.api.exception.UserNotFoundException; -import io.github.patternatlas.api.repositories.UserRepository; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; @Service @Transactional -public class UserServiceImpl implements UserService { +public class UserServiceImpl implements UserService, UserAuthService { - private final UserRepository userRepository; + private UserRepository userRepository; + private RoleRepository roleRepository; + private PrivilegeRepository privilegeRepository; + private final PasswordEncoder passwordEncoder; Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); - public UserServiceImpl(UserRepository userRepository) { + public UserServiceImpl( + UserRepository userRepository, + RoleRepository roleRepository, + PrivilegeRepository privilegeRepository, + PasswordEncoder passwordEncoder) { this.userRepository = userRepository; + this.roleRepository = roleRepository; + this.privilegeRepository = privilegeRepository; + this.passwordEncoder = passwordEncoder; + } + + @Override + @Transactional + public UserEntity saveUser(UserEntity user) { + return this.userRepository.save(user); } @Override @Transactional - public UserEntity createUser(UserEntity user) { - if (null == user) { - throw new NullUserException(); + public UserEntity createUser(UserModelRequest userModelRequest) { + if (null == userModelRequest) + throw new RuntimeException("User to update is null!"); + // DEFAULT password + if (userModelRequest.getPassword() == null) + throw new RuntimeException("Password is null"); + + + UserEntity user = new UserEntity(userModelRequest, passwordEncoder.encode(userModelRequest.getPassword())); + if(userModelRequest.getRoles() != null) { + userModelRequest.getRoles().stream().forEach(role -> { + if (this.roleRepository.findByName(role.getName()) != null) { + user.getRoles().add(this.roleRepository.findByName(role.getName())); + } else { + user.getRoles().add(this.roleRepository.findByName(RoleConstant.MEMBER)); + } + }); } - UserEntity newUser = this.userRepository.save(user); - logger.info(String.format("Create Issue %s: ", newUser.toString())); - return newUser; + + return this.userRepository.save(user); + } + + @Override + @Transactional(readOnly = true) + public List getAllUsers() { + return this.userRepository.findAll(); } @Override @Transactional(readOnly = true) public UserEntity getUserById(UUID userId) { return this.userRepository.findById(userId) - .orElseThrow(() -> new UserNotFoundException(userId)); + .orElseThrow(() -> new ResourceNotFoundException(String.format("User with ID %s not found!", userId))); + } + + private UserEntity createInitialUser(UserModelRequest userModelRequest, String requestedRole) { + UserEntity user = new UserEntity(userModelRequest, ""); + user.setEmail(""); + user.getRoles().add(this.roleRepository.findByName(requestedRole)); + + return this.userRepository.save(user); } @Override - @Transactional(readOnly = true) - public List getAllUsers() { - return this.userRepository.findAll(); + public UserEntity createInitialMember(UserModelRequest userModelRequest) { + return createInitialUser(userModelRequest, RoleConstant.MEMBER); + } + + @Override + public UserEntity createInitialAdmin(UserModelRequest userModelRequest) { + logger.info("Automatically creating admin user " + userModelRequest.getName()); + + return createInitialUser(userModelRequest, RoleConstant.ADMIN); + } + + @Override + public boolean userExists(UUID userId) { + return this.userRepository.findById(userId).isPresent(); + } + + @Override + public boolean hasUsers() { + return this.userRepository.count() > 0; } @Override @Transactional - public UserEntity updateUser(UserEntity user) { - if (null == user) { - throw new NullUserException(); - } - if (!this.userRepository.existsById(user.getId())) { - throw new UserNotFoundException(user.getId()); + public UserEntity updateUser(UUID userId, UserModelRequest userModelRequest) { + if (userModelRequest == null) + throw new RuntimeException("User to update is null!"); + userModelRequest.getRoles().stream().forEach(role -> { + if (!this.roleRepository.existsByName(role.getName())) + throw new ResourceNotFoundException(String.format("User Role %s not found!", role)); + }); + + UserEntity user = this.getUserById(userId); + userModelRequest.getRoles().stream().forEach(role -> { + if (!user.getRoles().stream().map(r -> r.getName()).collect(Collectors.toList()).contains(role)) + user.getRoles().add(this.roleRepository.findByName(role.getName())); + }); + if (userModelRequest.getOldPassword() != null || userModelRequest.getPassword() != null) { + if (!passwordEncoder.matches(userModelRequest.getOldPassword(), user.getPassword()) && !user.getRoles().stream().map(role -> role.getName()).collect(Collectors.toList()).contains(RoleConstant.ADMIN)) + throw new ResourceNotFoundException(String.format("Password ERROR")); + user.setPassword(passwordEncoder.encode(userModelRequest.getPassword())); } + user.updateUserEntity(userModelRequest); return this.userRepository.save(user); } @Override @Transactional public void deleteUser(UUID userId) { + this.getUserById(userId); + this.userRepository.deleteById(userId); + } + + @Override + public UserEntity updatePlatformRole(UUID userId, UserModelRequest userModelRequest) { + if (userModelRequest == null) + throw new RuntimeException("User to update is null!"); + UserEntity user = this.getUserById(userId); - if (null == user) { - throw new NullUserException(); + logger.warn("Request: {}", userModelRequest); + // Find new supplied platform wide role (should be only one) + if(userModelRequest.getRoles() != null) { + Optional requestedRole = + userModelRequest.getRoles().stream() + .filter(role -> RoleConstant.PLATFORM_ROLES.contains(role.getName())) + .findFirst(); + if(requestedRole.isPresent()) { + // Find old existing platform role + List existingRoles = user.getRoles().stream() + .filter(role -> RoleConstant.PLATFORM_ROLES.contains(role.getName())) + .collect(Collectors.toList()); + if(!existingRoles.isEmpty()) { + user.getRoles().removeAll(existingRoles); + user.getRoles().add(this.roleRepository.findByName(requestedRole.get().getName())); + user = this.saveUser(user); + } + } } - this.userRepository.deleteById(userId); + return user; + } + + @Override + public boolean hasAnyPrivilege(UUID userId, String... permissions) { + for(String permission : permissions) { + if(this.privilegeRepository.existsPrivilegeForUser(permission, userId)) { + return true; + } + } + return false; + } + + /** + * Role && Permission + */ + @Override + @Transactional(readOnly = true) + public List getAllRoles() { + return this.roleRepository.findAll(); + } + + @Override + @Transactional(readOnly = true) + public List getAllPlatformRoles() { + return this.roleRepository.findAllRolesByNames(RoleConstant.PLATFORM_ROLES); + } + + @Override + @Transactional(readOnly = true) + public List getAllAuthorRoles() { + return this.roleRepository.findAllRolesByNames(RoleConstant.AUTHOR_ROLES); + } + + @Override + @Transactional(readOnly = true) + public List getAllRolesFromEntity(UUID entityId) { + return this.roleRepository.findAllFromEntity(entityId); + } + + @Override + @Transactional(readOnly = true) + public List getAllPlatformPrivileges() { + return this.privilegeRepository.findAllPlatformPrivileges(); + } + + @Override + @Transactional(readOnly = true) + public List getAllDefaultAuthorPrivileges() { + return this.privilegeRepository.findAllDefaultAuthorPrivileges(); + } + + @Override + @Transactional(readOnly = true) + public List getAllPrivilegesFromEntity(UUID entityId) { + return this.privilegeRepository.findAllFromEntity(entityId); + } + + @Override + @Transactional + public Role updateRole(UUID roleId, UUID privilegeId, RoleModelRequest roleModelRequest) { + if (roleModelRequest == null) + throw new RuntimeException("Role to update is null!"); + + Role role = this.roleRepository.findById(roleId).orElseThrow(() -> new ResourceNotFoundException(String.format(" Role %s not found!", roleId))); + Privilege privilege = this.privilegeRepository.findById(privilegeId).orElseThrow(() -> new ResourceNotFoundException(String.format("Privilege %s not found!", privilegeId))); + + if (role.getPrivileges().contains(privilege) && !roleModelRequest.isCheckboxValue()) { + role.getPrivileges().remove(privilege); + } + + if (!role.getPrivileges().contains(privilege) && roleModelRequest.isCheckboxValue()) { + role.getPrivileges().add(privilege); + } + + return this.roleRepository.save(role); + } + + @Override + @Transactional + public void updateAllResourceSpecificRoles(UUID authorRoleId, UUID defaultAuthorPrivilegeId, RoleModelRequest roleModelRequest) { + Role authorRole = this.roleRepository.findById(authorRoleId).orElseThrow(() -> new ResourceNotFoundException(String.format("Role %s not found!", authorRoleId))); + Privilege defaultAuthorPrivilege = this.privilegeRepository.findById(defaultAuthorPrivilegeId).orElseThrow(() -> new ResourceNotFoundException(String.format("Privilege %s not found!", defaultAuthorPrivilegeId))); + List privileges = this.privilegeRepository.findAllResourceSpecific(defaultAuthorPrivilege.getName()); + + if (roleModelRequest.isCheckboxValue()) { + for (Privilege privilege : privileges) { + String privilegeName = privilege.getName(); + String uuidString = privilegeName.substring(privilegeName.length() - 36); + UUID resourceId = UUID.fromString(uuidString); + + List roles = this.roleRepository.findAllFromEntityForAuthorRole(resourceId, authorRole.getName()); + + if (roles.size() > 1) { + logger.warn(String.format("Found more than one role %s for resource ID %s", authorRole.getName(), resourceId)); + } + + if (roles.size() == 0) { + logger.warn(String.format("Could not find role %s for resource ID %s", authorRole.getName(), resourceId)); + } else { + roles.get(0).addPrivilege(privilege); + } + } + } else { + List authors = this.roleRepository.findAllForAuthorRole(authorRole.getName()); + + for (Role author : authors) { + Collection privilegesOfAuthor = author.getPrivileges(); + + for (Privilege privilege : privileges) { + if (privilegesOfAuthor.contains(privilege)) { + author.removePrivilege(privilege); + } + } + } + } } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/patternatlas/api/util/UserIdGenerator.java b/src/main/java/io/github/patternatlas/api/util/UserIdGenerator.java new file mode 100644 index 0000000..68705ab --- /dev/null +++ b/src/main/java/io/github/patternatlas/api/util/UserIdGenerator.java @@ -0,0 +1,18 @@ +package io.github.patternatlas.api.util; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.id.UUIDGenerator; + +import java.io.Serializable; + +public class UserIdGenerator extends UUIDGenerator { + @Override + public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException { + Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session); + if(id == null) { + id = super.generate(session, object); + } + return id; + } +} diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties index 667ed35..ca87b57 100644 --- a/src/main/resources/application-docker.properties +++ b/src/main/resources/application-docker.properties @@ -1,17 +1,13 @@ server.port=1977 -spring.datasource.url=jdbc:postgresql://localhost:5432/postgres -spring.datasource.username=postgres -spring.datasource.password=postgres +spring.datasource.url=jdbc:postgresql://localhost:5060/patternatlas +spring.datasource.username=patternatlas +spring.datasource.password=patternatlas spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true spring.jpa.hibernate.ddl-auto=update logging.level.io.github.patternatlas.api=debug spring.datasource.initialization-mode=always spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true -springdoc.swagger-ui.url=/patternpedia/v3/api-docs -springdoc.swagger-ui.path=/swagger-ui -springdoc.swagger-ui.config-url=/patternpedia/v3/api-docs/swagger-config -springdoc.swagger-ui.operationsSorter=alpha springdoc.default-produces-media-type=application/hal+json security.oauth2.resource.jwk.key-set-uri=http://localhost:8081/.well-known/jwks.json #okta.oauth2.issuer=https://dev-918271.okta.com/oauth2/default diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 400ffe5..0435388 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,19 +1,18 @@ server.port=1977 -spring.datasource.url=jdbc:postgresql://localhost:5060/patternatlas +spring.datasource.url=jdbc:postgresql://localhost:5432/patternatlas spring.datasource.username=patternatlas spring.datasource.password=patternatlas spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true spring.jpa.hibernate.ddl-auto=update -logging.level.io.github.patternatlas.api=debug +logging.level.io.github.patternatlas.api=info spring.datasource.initialization-mode=always spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true -springdoc.swagger-ui.url=/patternatlas/v3/api-docs -springdoc.swagger-ui.path=/swagger-ui -springdoc.swagger-ui.config-url=/patternatlas/v3/api-docs/swagger-config -springdoc.swagger-ui.operationsSorter=alpha springdoc.default-produces-media-type=application/hal+json -security.oauth2.resource.jwk.key-set-uri=http://localhost:8081/.well-known/jwks.json +# alt: AUTH-Server +# security.oauth2.resource.jwk.key-set-uri=http://localhost:8081/.well-known/jwks.json +# neu: Keycloak +security.oauth2.resource.jwk.key-set-uri=http://localhost:8080/realms/patternatlas/protocol/openid-connect/certs #okta.oauth2.issuer=https://dev-918271.okta.com/oauth2/default #okta.oauth2.clientId=0oa1eflyl1wZDVLLg357 #---------------------------- @@ -24,4 +23,4 @@ io.github.patternatlas.api.latexrenderer.port=5030 # Embedded Tomcat server.servlet.contextPath=/patternatlas # liquibase file -spring.liquibase.change-log=file:patternatlas.xml +spring.liquibase.change-log=patternatlas.xml diff --git a/src/main/resources/applicationYML.yml b/src/main/resources/applicationYML.yml index 6e715a7..b670a8f 100644 --- a/src/main/resources/applicationYML.yml +++ b/src/main/resources/applicationYML.yml @@ -3,9 +3,9 @@ server: spring: datasource: - url: jdbc:postgresql://localhost:5432/postgres - username: postgres - password: postgres + url: jdbc:postgresql://localhost:5060/patternatlas + username: patternatlas + password: patternatlas initialization-mode: always jpa: hibernate: @@ -29,7 +29,3 @@ security: jwk: key-set-uri: http://localhost:8081/.well-known/jwks.json -springdoc: - swagger-ui: - path: /swagger-ui.html - diff --git a/src/main/resources/db/migration/data.sql b/src/main/resources/db/migration/data.sql new file mode 100644 index 0000000..b6f7ba9 --- /dev/null +++ b/src/main/resources/db/migration/data.sql @@ -0,0 +1,6 @@ +INSERT INTO user_entity VALUES ('6f6f9111-8d88-4b65-8dca-7b14c0e354ae', 'admin@mail', 'admin', '$2a$10$hoLdIpsbbYlOts0zIqIH5uucveGPIwhs4whXIUanKm2ISEaOjhkIG'); +INSERT INTO user_entity_roles VALUES ('6f6f9111-8d88-4b65-8dca-7b14c0e354ae', 'MEMBER'); +INSERT INTO user_entity_roles VALUES ('6f6f9111-8d88-4b65-8dca-7b14c0e354ae', 'ADMIN'); + +INSERT INTO user_entity VALUES ('92cc4758-4ed6-4159-9c69-66fd8f6eab35', 'member@mail', 'member', '$2a$10$hoLdIpsbbYlOts0zIqIH5uucveGPIwhs4whXIUanKm2ISEaOjhkIG'); +INSERT INTO user_entity_roles VALUES ('92cc4758-4ed6-4159-9c69-66fd8f6eab35', 'MEMBER'); diff --git a/src/test/java/io/github/patternatlas/api/integration/IssueControllerTest.java b/src/test/java/io/github/patternatlas/api/integration/IssueControllerTest.java new file mode 100644 index 0000000..5b52bdb --- /dev/null +++ b/src/test/java/io/github/patternatlas/api/integration/IssueControllerTest.java @@ -0,0 +1,4 @@ +package io.github.patternatlas.api.integration; + +public class IssueControllerTest { +}