Skip to content

Commit

Permalink
Merge pull request #468 from puzzle/feature/331-objective-draftstate
Browse files Browse the repository at this point in the history
Feature/331: Objective Draft State and Release
  • Loading branch information
peggimann authored Oct 19, 2023
2 parents a666ccb + 23cd88b commit 622cafc
Show file tree
Hide file tree
Showing 37 changed files with 1,078 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ch.puzzle.okr.controller;

import ch.puzzle.okr.models.Completed;
import ch.puzzle.okr.service.business.CompletedBusinessService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("api/v2/completed")
public class CompletedController {

private final CompletedBusinessService completedBusinessService;

public CompletedController(CompletedBusinessService completedBusinessService) {
this.completedBusinessService = completedBusinessService;
}

@Operation(summary = "Create Completed", description = "Create a new Completed Reference.")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Created new Completed.", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = Completed.class)) }),
@ApiResponse(responseCode = "404", description = "Could not create Completed Reference", content = @Content) })
@PostMapping
public ResponseEntity<Completed> createCompleted(@RequestBody Completed completed) {
Completed createdCompleted = completedBusinessService.createCompleted(completed);
return ResponseEntity.status(HttpStatus.CREATED).body(createdCompleted);
}

@Operation(summary = "Delete Completed by Objective Id", description = "Delete Completed Reference by Objective Id")
@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Deleted Completed by Objective Id"),
@ApiResponse(responseCode = "404", description = "Did not find the Completed with requested Objective id") })
@DeleteMapping("/{objectiveId}")
public void deleteCompletedByObjectiveId(@PathVariable long objectiveId) {
completedBusinessService.deleteCompletedByObjectiveId(objectiveId);
}
}
102 changes: 102 additions & 0 deletions backend/src/main/java/ch/puzzle/okr/models/Completed.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package ch.puzzle.okr.models;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Objects;

@Entity
public class Completed {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence_objective")
private Long id;

@NotNull(message = "Objective must not be null")
@OneToOne
private Objective objective;

@Size(max = 4096, message = "Attribute comment has a max length of 4096 characters when completing an objective")
private String comment;

public Completed() {
}

private Completed(Builder builder) {
id = builder.id;
setObjective(builder.objective);
setComment(builder.comment);
}

public Long getId() {
return id;
}

public Objective getObjective() {
return objective;
}

public void setObjective(Objective objective) {
this.objective = objective;
}

public String getComment() {
return comment;
}

public void setComment(String comment) {
this.comment = comment;
}

@Override
public String toString() {
return "Completed{" + "id=" + id + ", objective=" + objective + ", comment='" + comment + '\'' + '}';
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Completed completed = (Completed) o;
return Objects.equals(id, completed.id) && Objects.equals(objective, completed.objective)
&& Objects.equals(comment, completed.comment);
}

@Override
public int hashCode() {
return Objects.hash(id, objective, comment);
}

public static final class Builder {
private Long id;
private Objective objective;
private String comment;

private Builder() {
}

public static Builder builder() {
return new Builder();
}

public Builder withId(Long id) {
this.id = id;
return this;
}

public Builder withObjective(Objective objective) {
this.objective = objective;
return this;
}

public Builder withComment(String comment) {
this.comment = comment;
return this;
}

public Completed build() {
return new Completed(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ch.puzzle.okr.repository;

import ch.puzzle.okr.models.Completed;
import org.springframework.data.repository.CrudRepository;

public interface CompletedRepository extends CrudRepository<Completed, Long> {
Completed findByObjectiveId(Long objectiveId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ch.puzzle.okr.service.business;

import ch.puzzle.okr.models.Completed;
import ch.puzzle.okr.service.persistence.CompletedPersistenceService;
import ch.puzzle.okr.service.validation.CompletedValidationService;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service
public class CompletedBusinessService {
private final CompletedPersistenceService completedPersistenceService;
private final CompletedValidationService validator;

public CompletedBusinessService(CompletedPersistenceService completedPersistenceService,
CompletedValidationService validator) {
this.completedPersistenceService = completedPersistenceService;
this.validator = validator;
}

@Transactional
public Completed createCompleted(Completed completed) {
validator.validateOnCreate(completed);
return completedPersistenceService.save(completed);
}

@Transactional
public void deleteCompletedByObjectiveId(Long objectiveId) {
Completed completed = completedPersistenceService.getCompletedByObjectiveId(objectiveId);
validator.validateOnDelete(completed.getId());
completedPersistenceService.deleteById(completed.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public Objective getObjectiveById(Long id) {
@Transactional
public Objective updateObjective(Long id, Objective objective, Jwt token) {
Objective savedObjective = objectivePersistenceService.findById(id);
objective.setState(savedObjective.getState());
objective.setCreatedBy(savedObjective.getCreatedBy());
objective.setCreatedOn(savedObjective.getCreatedOn());
objective.setModifiedBy(userBusinessService.getUserByAuthorisationToken(token));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ch.puzzle.okr.service.persistence;

import ch.puzzle.okr.models.Completed;
import ch.puzzle.okr.repository.CompletedRepository;
import org.springframework.stereotype.Service;

@Service
public class CompletedPersistenceService extends PersistenceBase<Completed, Long> {

protected CompletedPersistenceService(CompletedRepository repository) {
super(repository);
}

@Override
public String getModelName() {
return "Completed";
}

public Completed getCompletedByObjectiveId(Long objectiveId) {
return ((CompletedRepository) this.repository).findByObjectiveId(objectiveId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ch.puzzle.okr.service.validation;

import ch.puzzle.okr.models.Completed;
import ch.puzzle.okr.service.persistence.CompletedPersistenceService;
import org.springframework.stereotype.Service;

@Service
public class CompletedValidationService extends ValidationBase<Completed, Long> {

public CompletedValidationService(CompletedPersistenceService completedPersistenceService) {
super(completedPersistenceService);
}

@Override
public void validateOnCreate(Completed model) {
throwExceptionIfModelIsNull(model);
throwExceptionWhenIdIsNotNull(model.getId());
validate(model);
}

@Override
public void validateOnUpdate(Long id, Completed model) {
throw new IllegalCallerException("This method must not be called because there is no update of completed");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ truncate table person;
truncate table quarter;
truncate table team;
truncate table alignment;
truncate table completed;

SET REFERENTIAL_INTEGRITY TRUE;

Expand All @@ -17,6 +18,7 @@ ALTER SEQUENCE sequence_objective RESTART WITH 200;
ALTER SEQUENCE sequence_key_result RESTART WITH 200;
ALTER SEQUENCE sequence_check_in RESTART WITH 200;
ALTER SEQUENCE sequence_alignment RESTART WITH 200;
ALTER SEQUENCE sequence_completed RESTART WITH 200;

insert into person (id, email, firstname, lastname, username)
values (1, '[email protected]', 'Paco', 'Eggimann', 'peggimann'),
Expand Down Expand Up @@ -109,4 +111,9 @@ insert into alignment (id, aligned_objective_id, alignment_type, target_key_resu
(1, 4, 'objective', null, 3),
(2, 4, 'keyResult', 8, null);

);
);

insert into completed (id, objective_id, comment) values
(1, 4, 'Das hat geklappt'),
(2, 6, 'War leider nicht moeglich'),
(3, 10, 'Schade');
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ create sequence if not exists sequence_person;
create sequence if not exists sequence_quarter;
create sequence if not exists sequence_team;
create sequence if not exists sequence_alignment;
create sequence if not exists sequence_completed;

create table if not exists person
(
Expand Down Expand Up @@ -109,6 +110,16 @@ create table if not exists key_result
foreign key (owner_id) references person
);

create table if not exists completed
(
id bigint not null primary key,
objective_id bigint not null
constraint fk_completed_objective
references objective,
comment varchar(4096)
);


DROP VIEW IF EXISTS OVERVIEW;
CREATE VIEW OVERVIEW AS
SELECT TQ.TEAM_ID AS "TEAM_ID",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
create sequence if not exists sequence_completed;

create table if not exists completed
(
id bigint not null primary key,
objective_id bigint not null
constraint fk_completed_objective
references objective,
comment varchar(4096)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package ch.puzzle.okr.controller;

import ch.puzzle.okr.models.Completed;
import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.service.business.CompletedBusinessService;
import org.hamcrest.core.Is;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.BDDMockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.web.server.ResponseStatusException;

import static ch.puzzle.okr.KeyResultTestHelpers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doThrow;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@WithMockUser(value = "spring")
@ExtendWith(MockitoExtension.class)
@WebMvcTest(CompletedController.class)
class CompleteControllerIT {

@MockBean
CompletedBusinessService completedBusinessService;

@Autowired
private MockMvc mvc;

Completed successfulCompleted = Completed.Builder.builder().withId(1L)
.withObjective(Objective.Builder.builder().withId(3L).withTitle("Gute Lernende").build())
.withComment("Wir haben es gut geschafft").build();

public static final String createBodySuccessful = """
{
"id":null,
"objectiveId":3,
"comment":"Wir haben es gut geschafft"
}
""";

String baseUrl = "/api/v2/completed";

@Test
void createSuccessfulCompleted() throws Exception {
BDDMockito.given(this.completedBusinessService.createCompleted((any()))).willReturn(successfulCompleted);

mvc.perform(post(baseUrl).content(createBodySuccessful).contentType(MediaType.APPLICATION_JSON)
.with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(MockMvcResultMatchers.status().is2xxSuccessful()).andExpect(jsonPath(JSON_PATH_ID, Is.is(1)))
.andExpect(jsonPath("$.objective.id", Is.is(3)))
.andExpect(jsonPath("$.comment", Is.is("Wir haben es gut geschafft")));
}

@Test
void shouldDeleteCompleted() throws Exception {
mvc.perform(delete("/api/v2/completed/1").with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(MockMvcResultMatchers.status().isOk());
}

@Test
void throwExceptionWhenCompletedWithIdCantBeFoundWhileDeleting() throws Exception {
doThrow(new ResponseStatusException(HttpStatus.NOT_FOUND, "Completed not found")).when(completedBusinessService)
.deleteCompletedByObjectiveId(anyLong());

mvc.perform(delete("/api/v2/completed/1000").with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(MockMvcResultMatchers.status().isNotFound());
}
}
Loading

0 comments on commit 622cafc

Please sign in to comment.