Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5d61fd6
feat: add role entity class #34
lcanobbio Sep 5, 2025
d959760
feat: add role dto #34
lcanobbio Sep 5, 2025
f54e220
feat: add role mapper and rename dto class #34
lcanobbio Sep 5, 2025
0d8a1d8
feat: add role repository #34
lcanobbio Sep 5, 2025
4934493
feat: add validation business and persitience service #34
lcanobbio Sep 5, 2025
30ca498
feat: add role migration #34
lcanobbio Sep 5, 2025
aa030ea
refactor: add attribute isManagement to role #34
lcanobbio Sep 5, 2025
bee7f04
feat: add role controller and add isManagement to migration #34
lcanobbio Sep 5, 2025
4a6673a
feat: add all enpoints and modified migration #34
lcanobbio Sep 8, 2025
0c175b7
fix: review improvements #34
lcanobbio Sep 9, 2025
06886ec
fix: change datatype from varchar to text #34
lcanobbio Sep 9, 2025
c42de2f
fix: change from @where to @sqlrestriction because where is obsoltetd…
lcanobbio Sep 9, 2025
376f690
fix: rename file like okr and change id assigment #34
lcanobbio Sep 9, 2025
4c0de31
fix: internal server error when searching for a not existing role #34
nevio18324 Sep 10, 2025
5fddbb0
fix: internal server error when searching for a not existing role #34
nevio18324 Sep 10, 2025
55f43e1
fix: change namo of test-data-migration to avoid version problems #34
nevio18324 Sep 10, 2025
77ee84c
test: add all tests about role #34
lcanobbio Sep 12, 2025
2501a82
chore: change sonar warnings #34
lcanobbio Sep 12, 2025
47ecc33
fix: naming and delete service returns void #34
schiltpuzzle Sep 15, 2025
53c4bc8
fix: review improvements #34
lcanobbio Sep 15, 2025
5b2af87
chore: format one test and set default value to isManagement
lcanobbio Sep 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package ch.puzzle.pcts.controller;

import ch.puzzle.pcts.dto.role.RoleDto;
import ch.puzzle.pcts.mapper.RoleMapper;
import ch.puzzle.pcts.model.role.Role;
import ch.puzzle.pcts.service.business.RoleBusinessService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
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 jakarta.validation.Valid;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/roles")
public class RoleController {
private final RoleMapper mapper;
private final RoleBusinessService service;

@Autowired
public RoleController(RoleMapper mapper, RoleBusinessService service) {
this.mapper = mapper;
this.service = service;
}

@Operation(summary = "List all roles")
@ApiResponse(responseCode = "200", description = "A list of roles", content = {
@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = RoleDto.class))) })
@GetMapping
public ResponseEntity<List<RoleDto>> getRole() {
return ResponseEntity.ok(mapper.toDto(service.getAll()));
}

@Operation(summary = "Get a role by ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "A single role", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = RoleDto.class)) }),
@ApiResponse(responseCode = "404", description = "Role not found", content = @Content) })
@GetMapping("{id}")
public ResponseEntity<RoleDto> getRoleById(@PathVariable long id) {
Role role = service.getById(id);
return ResponseEntity.ok(mapper.toDto(role));
}

@Operation(summary = "Create a new role")
@ApiResponse(responseCode = "201", description = "Role created successfully", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = RoleDto.class)) })
@PostMapping
public ResponseEntity<RoleDto> createNew(@Valid @RequestBody RoleDto dto) {
Role newRole = service.create(mapper.fromDto(dto));
return ResponseEntity.status(201).body(mapper.toDto(newRole));
}

@Operation(summary = "Update a role")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Role updated successfully", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = RoleDto.class)) }),
@ApiResponse(responseCode = "404", description = "Role not found", content = @Content) })
@PutMapping("{id}")
public ResponseEntity<RoleDto> updateRole(@PathVariable Long id, @RequestBody RoleDto dto) {
Role updatedRole = service.update(id, mapper.fromDto(dto));
return ResponseEntity.ok(mapper.toDto(updatedRole));
}

@Operation(summary = "Delete a role")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Role deleted successfully", content = @Content),
@ApiResponse(responseCode = "404", description = "Role not found", content = @Content) })
@DeleteMapping("{id}")
public ResponseEntity<Void> deleteRole(@PathVariable Long id) {
service.delete(id);
return ResponseEntity.status(204).build();
}
}
4 changes: 4 additions & 0 deletions backend/src/main/java/ch/puzzle/pcts/dto/role/RoleDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ch.puzzle.pcts.dto.role;

public record RoleDto(Long id, String name, boolean isManagement) {
}
26 changes: 26 additions & 0 deletions backend/src/main/java/ch/puzzle/pcts/mapper/RoleMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ch.puzzle.pcts.mapper;

import ch.puzzle.pcts.dto.role.RoleDto;
import ch.puzzle.pcts.model.role.Role;
import java.util.List;
import org.springframework.stereotype.Component;

@Component
public class RoleMapper {

public List<RoleDto> toDto(List<Role> models) {
return models.stream().map(this::toDto).toList();
}

public List<Role> fromDto(List<RoleDto> dtos) {
return dtos.stream().map(this::fromDto).toList();
}

public RoleDto toDto(Role model) {
return new RoleDto(model.getId(), model.getName(), model.getIsManagement());
}

public Role fromDto(RoleDto dto) {
return new Role(dto.id(), dto.name(), dto.isManagement());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ public enum ErrorKey {
VALIDATION,
VALIDATION_DOES_NOT_INCLUDE,
INTERNAL,
ID_IS_NOT_NULL
ID_IS_NOT_NULL,
NOT_FOUND,
ROLE_NAME_IS_NULL,
ROLE_NAME_IS_EMPTY,
}
51 changes: 51 additions & 0 deletions backend/src/main/java/ch/puzzle/pcts/model/role/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ch.puzzle.pcts.model.role;

import jakarta.persistence.*;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;

@Entity
@SQLDelete(sql = "UPDATE role SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?")
@SQLRestriction("deleted_at IS NULL")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

private boolean isManagement;

public Role(Long id, String name, boolean isManagement) {
this.id = id;
this.name = name;
this.isManagement = isManagement;
}

public Role() {
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public boolean getIsManagement() {
return this.isManagement;
}

public void setIsManagement(boolean isManagement) {
this.isManagement = isManagement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ch.puzzle.pcts.repository;

import ch.puzzle.pcts.model.role.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ch.puzzle.pcts.service.business;

import ch.puzzle.pcts.exception.PCTSException;
import ch.puzzle.pcts.model.error.ErrorKey;
import ch.puzzle.pcts.model.role.Role;
import ch.puzzle.pcts.service.persistence.RolePersistenceService;
import ch.puzzle.pcts.service.validation.RoleValidationService;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

@Service
public class RoleBusinessService {
private final RoleValidationService validationService;
private final RolePersistenceService persistenceService;

public RoleBusinessService(RoleValidationService validationService, RolePersistenceService persistenceService) {
this.validationService = validationService;
this.persistenceService = persistenceService;
}

public List<Role> getAll() {
return persistenceService.getAll();
}

public Role getById(long id) {
validationService.validateOnGetById(id);
return persistenceService
.getById(id)
.orElseThrow(() -> new PCTSException(HttpStatus.NOT_FOUND,
"Role with id: " + id + " does not exist.",
ErrorKey.NOT_FOUND));
}

public Role create(Role role) {
validationService.validateOnCreate(role);
return persistenceService.create(role);
}

public Role update(Long id, Role role) {
validationService.validateOnUpdate(id, role);
return persistenceService.update(id, role);
}

public void delete(Long id) {
validationService.validateOnDelete(id);
persistenceService.delete(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ch.puzzle.pcts.service.persistence;

import ch.puzzle.pcts.model.role.Role;
import ch.puzzle.pcts.repository.RoleRepository;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class RolePersistenceService {
private final RoleRepository repository;

@Autowired
public RolePersistenceService(RoleRepository repository) {
this.repository = repository;
}

public Role create(Role role) {
return repository.save(role);
}

public Optional<Role> getById(long id) {
return repository.findById(id);
}

public List<Role> getAll() {
return repository.findAll();
}

public Role update(Long id, Role role) {
role.setId(id);
return repository.save(role);
}

public void delete(Long id) {
repository.deleteById(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import ch.puzzle.pcts.exception.PCTSException;
import ch.puzzle.pcts.model.error.ErrorKey;
import ch.puzzle.pcts.model.example.Example;
import ch.puzzle.pcts.service.persistence.ExamplePersistenceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

Expand All @@ -14,13 +12,6 @@
*/
@Component
public class ExampleValidationService {
private final ExamplePersistenceService persistenceService;

@Autowired
public ExampleValidationService(ExamplePersistenceService persistenceService) {
this.persistenceService = persistenceService;
}

public void validateOnCreate(Example example) {

if (!example.getText().contains("Example")) {
Expand Down
Comment thread
nevio18324 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package ch.puzzle.pcts.service.validation;

import ch.puzzle.pcts.exception.PCTSException;
import ch.puzzle.pcts.model.error.ErrorKey;
import ch.puzzle.pcts.model.role.Role;
import ch.puzzle.pcts.service.persistence.RolePersistenceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

@Component
public class RoleValidationService {
private final RolePersistenceService persistenceService;

@Autowired
public RoleValidationService(RolePersistenceService persistenceService) {
this.persistenceService = persistenceService;
}

public void validateOnGetById(Long id) {
validateIfExists(id);
}

public void validateOnCreate(Role role) {
validateIfIdIsNull(role.getId());
validateName(role.getName());
}

public void validateOnDelete(Long id) {
validateIfExists(id);
}

public void validateOnUpdate(Long id, Role role) {
validateIfExists(id);
validateIfIdIsNull(role.getId());
validateName(role.getName());
}

private void validateIfIdIsNull(Long id) {
if (id != null) {
throw new PCTSException(HttpStatus.BAD_REQUEST, "Id needs to be undefined", ErrorKey.ID_IS_NOT_NULL);
}
}

private void validateName(String name) {
if (name == null) {
throw new PCTSException(HttpStatus.BAD_REQUEST, "Name must not be null", ErrorKey.ROLE_NAME_IS_NULL);
}

if (name.isBlank()) {
throw new PCTSException(HttpStatus.BAD_REQUEST, "Name must not be empty", ErrorKey.ROLE_NAME_IS_EMPTY);
}
}

private void validateIfExists(long id) {
persistenceService
.getById(id)
.orElseThrow(() -> new PCTSException(HttpStatus.NOT_FOUND,
"Role with id: " + id + " does not exist.",
ErrorKey.NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
TRUNCATE TABLE example CASCADE;


INSERT INTO example (id, text)
VALUES (1, 'Example 1'),
(2, 'Example 2'),
Expand All @@ -10,4 +9,14 @@ VALUES (1, 'Example 1'),

SELECT setval('sequence_example', (SELECT MAX(id) FROM example));

TRUNCATE TABLE role CASCADE;

INSERT INTO role (name, is_management)
VALUES
('Administrator', TRUE),
('Manager', TRUE),
('Team Lead', TRUE),
('Developer', FALSE),
('Intern', FALSE),
('Extern', FALSE),
('Consultant', FALSE);
7 changes: 7 additions & 0 deletions backend/src/main/resources/db/migration/V0_0_2__role.sql
Comment thread
MasterEvarior marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS role
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
deleted_at TIMESTAMP DEFAULT NULL,
is_management BOOLEAN NOT NULL DEFAULT FALSE
);
Loading
Loading