Skip to content

Commit 5e5c229

Browse files
committed
feat: Add migration and controll service #36
1 parent 09f1cd7 commit 5e5c229

9 files changed

Lines changed: 210 additions & 12 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package ch.puzzle.pcts.controller;
2+
3+
import ch.puzzle.pcts.dto.certificate.CertificateDto;
4+
import ch.puzzle.pcts.mapper.CertificateMapper;
5+
import ch.puzzle.pcts.service.business.CertificateBusinessService;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.media.ArraySchema;
8+
import io.swagger.v3.oas.annotations.media.Content;
9+
import io.swagger.v3.oas.annotations.media.Schema;
10+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
12+
import java.util.List;
13+
import org.springframework.http.ResponseEntity;
14+
import org.springframework.web.bind.annotation.*;
15+
16+
@RestController
17+
@RequestMapping("api/v1/certificates")
18+
public class CertificateController {
19+
private final CertificateBusinessService service;
20+
private final CertificateMapper mapper;
21+
22+
public CertificateController(CertificateBusinessService service, CertificateMapper mapper) {
23+
this.service = service;
24+
this.mapper = mapper;
25+
}
26+
27+
@Operation(summary = "List all Certificates")
28+
@ApiResponse(responseCode = "200", description = "A list off Certificates", content = {
29+
@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = CertificateDto.class))) })
30+
@GetMapping
31+
public ResponseEntity<List<CertificateDto>> getCertificate() {
32+
return ResponseEntity.ok(mapper.toDto(service.getAll()));
33+
}
34+
35+
@Operation(summary = "Get Certificate by ID")
36+
@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "A single certificate", content = {
37+
@Content(mediaType = "application/json", schema = @Schema(implementation = CertificateDto.class)) }),
38+
@ApiResponse(responseCode = "404", description = "Certificate not found", content = @Content) })
39+
@GetMapping("{id}")
40+
public ResponseEntity<CertificateDto> getCertificatesById(@PathVariable Long id) {
41+
return ResponseEntity.ok(mapper.toDto(service.getById(id)));
42+
}
43+
44+
@Operation(summary = "Create a new Certificate")
45+
@ApiResponse(responseCode = "201", description = "Certificate created successfully", content = {
46+
@Content(mediaType = "application/json", schema = @Schema(implementation = CertificateDto.class)) })
47+
@PostMapping
48+
public ResponseEntity<CertificateDto> createNew(@RequestBody CertificateDto dto) {
49+
return ResponseEntity.ok(mapper.toDto(service.create(mapper.fromDto(dto))));
50+
}
51+
52+
@Operation(summary = "Update a Certificate")
53+
@ApiResponses(value = {
54+
@ApiResponse(responseCode = "200", description = "Certificate updated successfully", content = {
55+
@Content(mediaType = "application/json", schema = @Schema(implementation = CertificateDto.class)) }),
56+
@ApiResponse(responseCode = "404", description = "Certificate not found", content = @Content) })
57+
@PutMapping("{id}")
58+
public ResponseEntity<CertificateDto> updateCertificate(@PathVariable Long id, @RequestBody CertificateDto dto) {
59+
return ResponseEntity.ok(mapper.toDto(service.update(id, mapper.fromDto(dto))));
60+
}
61+
62+
@Operation(summary = "Delete a Certificate")
63+
@ApiResponses(value = {
64+
@ApiResponse(responseCode = "204", description = "Certificate deleted successfully", content = @Content),
65+
@ApiResponse(responseCode = "404", description = "Certificate not found", content = @Content) })
66+
@DeleteMapping("{id}")
67+
public ResponseEntity<Void> deleteCertificate(@PathVariable Long id) {
68+
service.delete(id);
69+
return ResponseEntity.status(204).build();
70+
}
71+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
package ch.puzzle.pcts.dto.certificate;
22

3-
public record CertificateDto(Long id, String name, double points, String comment) {
3+
import java.math.BigDecimal;
4+
5+
public record CertificateDto(Long id, String name, BigDecimal points, String comment) {
46
}

backend/src/main/java/ch/puzzle/pcts/model/certificate/Certificate.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ch.puzzle.pcts.model.certificate;
22

33
import jakarta.persistence.*;
4+
import java.math.BigDecimal;
45

56
@Entity
67
public class Certificate {
@@ -11,14 +12,14 @@ public class Certificate {
1112
private String name;
1213

1314
@Column(nullable = false)
14-
private double points;
15+
private BigDecimal points;
1516

1617
@Column(nullable = false)
1718
private boolean is_deleted;
1819

1920
private String comment;
2021

21-
public Certificate(long id, String name, double points, boolean is_deleted, String comment) {
22+
public Certificate(Long id, String name, BigDecimal points, boolean is_deleted, String comment) {
2223
this.id = id;
2324
this.name = name;
2425
this.points = points;
@@ -45,11 +46,11 @@ public void setName(String name) {
4546
this.name = name;
4647
}
4748

48-
public double getPoints() {
49+
public BigDecimal getPoints() {
4950
return points;
5051
}
5152

52-
public void setPoints(double points) {
53+
public void setPoints(BigDecimal points) {
5354
this.points = points;
5455
}
5556

backend/src/main/java/ch/puzzle/pcts/service/business/CertificateBusinessService.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package ch.puzzle.pcts.service.business;
22

3+
import ch.puzzle.pcts.exception.PCTSException;
34
import ch.puzzle.pcts.model.certificate.Certificate;
5+
import ch.puzzle.pcts.model.error.ErrorKey;
46
import ch.puzzle.pcts.service.persistence.CertificatePersistenceService;
57
import ch.puzzle.pcts.service.validation.CertificateValidationService;
68
import java.util.List;
9+
import org.springframework.http.HttpStatus;
710
import org.springframework.stereotype.Service;
811

912
@Service
@@ -17,12 +20,31 @@ public CertificateBusinessService(CertificateValidationService certificateValida
1720
this.persistenceService = certificatePersistenceService;
1821
}
1922

20-
public List<Certificate> getAll() {
21-
23+
public Certificate create(Certificate certificate) {
24+
validationService.validateOnCreate(certificate);
25+
return persistenceService.create(certificate);
2226
}
2327

2428
public Certificate getById(long id) {
2529
validationService.validateOnGetById(id);
30+
return persistenceService
31+
.getById(id)
32+
.orElseThrow(() -> new PCTSException(HttpStatus.NOT_FOUND,
33+
"Certificate with id: " + id + "does not exist",
34+
ErrorKey.NOT_FOUND));
35+
}
36+
37+
public List<Certificate> getAll() {
38+
return persistenceService.getAll();
39+
}
40+
41+
public Certificate update(Long id, Certificate certificate) {
42+
validationService.validateOnUpdate(id, certificate);
43+
return persistenceService.update(id, certificate);
44+
}
45+
public void delete(Long id) {
46+
validationService.validateOnDelete(id);
47+
persistenceService.delete(id);
2648
}
2749

2850
}

backend/src/main/java/ch/puzzle/pcts/service/validation/CertificateValidationService.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,22 @@ public CertificateValidationService(CertificatePersistenceService persistenceSer
1818
}
1919

2020
public void validateOnCreate(Certificate certificate) {
21-
21+
validateIfIdIsNull(certificate.getId());
22+
validateName(certificate.getName());
2223
}
2324

2425
public void validateOnGetById(Long id) {
25-
26+
validateIfExists(id);
2627
}
2728

28-
public void validateOnUpdate(Certificate certificate) {
29-
29+
public void validateOnUpdate(Long id, Certificate certificate) {
30+
validateIfExists(id);
31+
validateIfIdIsNull(certificate.getId());
32+
validateName(certificate.getName());
3033
}
3134

3235
public void validateOnDelete(Long id) {
36+
validateIfExists(id);
3337
}
3438

3539
private void validateIfIdIsNull(Long id) {
@@ -52,7 +56,7 @@ private void validateIfExists(long id) {
5256
persistenceService
5357
.getById(id)
5458
.orElseThrow(() -> new PCTSException(HttpStatus.NOT_FOUND,
55-
"Role with id: " + id + " does not exist.",
59+
"Certificate with id: " + id + " does not exist.",
5660
ErrorKey.NOT_FOUND));
5761
}
5862
}

backend/src/main/resources/db/dev-data-migration/afterMigrate__0_0_initialData.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ VALUES
2020
('Intern', FALSE),
2121
('Extern', FALSE),
2222
('Consultant', FALSE);
23+
24+
TRUNCATE TABLE certificate CASCADE;
25+
26+
INSERT INTO certificate (name, points, is_deleted, comment)
27+
VALUES
28+
('Test', 2.5, false, 'This is a test')
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE TABLE IF NOT EXISTS certificate
2+
(
3+
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
4+
name TEXT,
5+
points NUMERIC NOT NULL,
6+
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
7+
comment TEXT
8+
)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package ch.puzzle.pcts.controller;
2+
3+
import static org.mockito.Mockito.*;
4+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
5+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
6+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
7+
8+
import ch.puzzle.pcts.SpringSecurityConfig;
9+
import ch.puzzle.pcts.dto.certificate.CertificateDto;
10+
import ch.puzzle.pcts.mapper.CertificateMapper;
11+
import ch.puzzle.pcts.model.certificate.Certificate;
12+
import ch.puzzle.pcts.service.business.CertificateBusinessService;
13+
import com.fasterxml.jackson.databind.ObjectMapper;
14+
import java.math.BigDecimal;
15+
import java.util.Collections;
16+
import org.junit.jupiter.api.BeforeEach;
17+
import org.junit.jupiter.api.DisplayName;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.extension.ExtendWith;
20+
import org.mockito.BDDMockito;
21+
import org.mockito.junit.jupiter.MockitoExtension;
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
24+
import org.springframework.context.annotation.Import;
25+
import org.springframework.http.MediaType;
26+
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
27+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
28+
import org.springframework.test.web.servlet.MockMvc;
29+
30+
@Import(SpringSecurityConfig.class)
31+
@ExtendWith(MockitoExtension.class)
32+
@WebMvcTest(CertificateController.class)
33+
public class CertificateControllerIT {
34+
35+
@MockitoBean
36+
private CertificateBusinessService service;
37+
38+
@MockitoBean
39+
private CertificateMapper mapper;
40+
41+
@Autowired
42+
private MockMvc mvc;
43+
44+
ObjectMapper objectMapper = new ObjectMapper();
45+
46+
private static final String BASEURL = "/api/v1/certificates";
47+
48+
private Certificate certificate;
49+
private CertificateDto requestDto;
50+
private CertificateDto dto;
51+
52+
@BeforeEach
53+
void setUp() {
54+
certificate = new Certificate(1L, "Certificate 1", new BigDecimal("5.5"), false, "This is Certificate 1");
55+
requestDto = new CertificateDto(null, "Certificate 1", new BigDecimal("5.5"), "This is Certificate 1");
56+
dto = new CertificateDto(1L, "Certificate 1", new BigDecimal("5.5"), "This is Certificate 1");
57+
}
58+
59+
@DisplayName("Should successfully get all Certificates")
60+
@Test
61+
void shouldGetAllCertificates() throws Exception {
62+
BDDMockito.given(service.getAll()).willReturn(Collections.singletonList(certificate));
63+
BDDMockito.given(mapper.toDto(certificate)).willReturn(dto);
64+
65+
mvc
66+
.perform(get(BASEURL)
67+
.with(SecurityMockMvcRequestPostProcessors.csrf())
68+
.accept(MediaType.APPLICATION_JSON))
69+
.andExpect(status().isOk())
70+
.andExpect(jsonPath("$.length()").value(1))
71+
.andExpect(jsonPath("$[0].id").value(dto.id()))
72+
.andExpect(jsonPath("$[0].name").value(dto.name()))
73+
.andExpect(jsonPath("$[0].points").value(dto.points()))
74+
.andExpect(jsonPath("$[0].comment").value(dto.comment()));
75+
76+
verify(service, times(1)).getAll();
77+
verify(mapper, times(1)).toDto(certificate);
78+
}
79+
}

backend/src/test/resources/db/test-data-migration/afterMigrate__0_0_initialData.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,10 @@ VALUES
1313
('Role 1', '1970-01-01 00:00:00', TRUE),
1414
('Role 2', null, FALSE);
1515

16+
TRUNCATE TABLE certificate CASCADE;
17+
18+
INSERT INTO certificate (name, points, is_deleted, comment)
19+
VALUES
20+
('Certificate 1', 5.5, false, 'This is Certificate 1')
1621

1722

0 commit comments

Comments
 (0)