Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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,47 @@
package school.faang.user_service.controller.goal;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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.RestController;
import school.faang.user_service.entity.goal.Goal;
import school.faang.user_service.entity.goal.dto.CreateGoalDto;
import school.faang.user_service.entity.goal.dto.UpdateGoalDto;
import school.faang.user_service.entity.goal.mapper.CreateGoalMapperImpl;
import school.faang.user_service.entity.goal.mapper.UpdateGoalMapperImpl;
import school.faang.user_service.service.GoalService;

@RestController
@RequestMapping("/api/v1/goal")
@RequiredArgsConstructor
public class GoalController {

private final GoalService goalService;
private final CreateGoalMapperImpl createGoalMapper;
private final UpdateGoalMapperImpl updateGoalMapper;

@PostMapping("/create")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostMapping самодостаточный для create, его лучше без параметров оставить, и по этой url /api/v1/goal делать create

public ResponseEntity<CreateGoalDto> createGoal(@Valid @RequestBody CreateGoalDto createGoalDto) {
Goal createdGoal = goalService.createGoal(
createGoalMapper.dtoToGoal(createGoalDto),
createGoalDto.skillsId(),
createGoalDto.parent()
);
return ResponseEntity.ok(createGoalMapper.goalToDto(createdGoal));
}

@PutMapping("/update/{goalId}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тоже /update не нужен в пути

public ResponseEntity<UpdateGoalDto> updateGoal(@PathVariable long goalId, @Valid @RequestBody UpdateGoalDto updateGoalDto) {
Goal createdGoal = goalService.update(
goalId,
updateGoalMapper.dtoToGoal(updateGoalDto),
updateGoalDto.skillsId()
);
return ResponseEntity.ok(updateGoalMapper.goalToDto(createdGoal));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package school.faang.user_service.entity.error;

public record DefaultErrorResponse(String code, String message) {}
6 changes: 4 additions & 2 deletions src/main/java/school/faang/user_service/entity/goal/Goal.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
import jakarta.persistence.TemporalType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import school.faang.user_service.entity.Skill;
Expand All @@ -28,7 +29,8 @@
import java.time.LocalDateTime;
import java.util.List;

@Data
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package school.faang.user_service.entity.goal.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.constraints.NotBlank;

import java.util.List;

@JsonInclude(JsonInclude.Include.NON_NULL)
public record CreateGoalDto(
Long id,
@NotBlank(message = "Empty goal title not allowed!") String title,
String description,
Long parent,
List<Long> skillsId
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package school.faang.user_service.entity.goal.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.constraints.NotBlank;
import school.faang.user_service.entity.goal.GoalStatus;

import java.util.List;

@JsonInclude(JsonInclude.Include.NON_NULL)
public record UpdateGoalDto(
@NotBlank(message = "Empty goal title not allowed!") String title,
String description,
GoalStatus status,
List<Long> skillsId
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package school.faang.user_service.entity.goal.mapper;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import school.faang.user_service.entity.Skill;
import school.faang.user_service.entity.goal.Goal;
import school.faang.user_service.entity.goal.dto.CreateGoalDto;

@Component
@RequiredArgsConstructor
public class CreateGoalMapperImpl implements GoalMapper<CreateGoalDto> {

@Override
public Goal dtoToGoal(CreateGoalDto createGoalDto) {
Goal goal = new Goal();
goal.setId(createGoalDto.id());
goal.setTitle(createGoalDto.title());
goal.setDescription(createGoalDto.description());
return goal;
}

@Override
public CreateGoalDto goalToDto(Goal goal) {
return new CreateGoalDto(
goal.getId(),
goal.getTitle(),
goal.getDescription(),
goal.getParent() != null ? goal.getParent().getId() : null,
goal.getSkillsToAchieve().stream().map(Skill::getId).toList()
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package school.faang.user_service.entity.goal.mapper;

import school.faang.user_service.entity.goal.Goal;

public interface GoalMapper<T> {
Goal dtoToGoal(T createGoalDto);
T goalToDto(Goal goal);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package school.faang.user_service.entity.goal.mapper;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import school.faang.user_service.entity.Skill;
import school.faang.user_service.entity.goal.Goal;
import school.faang.user_service.entity.goal.dto.UpdateGoalDto;

@Component
@RequiredArgsConstructor
public class UpdateGoalMapperImpl implements GoalMapper<UpdateGoalDto> {

@Override
public Goal dtoToGoal(UpdateGoalDto updateGoalDto) {
Goal goal = new Goal();
goal.setTitle(updateGoalDto.title());
goal.setDescription(updateGoalDto.description());
goal.setStatus(updateGoalDto.status());
return goal;
}

@Override
public UpdateGoalDto goalToDto(Goal goal) {
return new UpdateGoalDto(
goal.getTitle(),
goal.getDescription(),
goal.getStatus(),
goal.getSkillsToAchieve().stream().map(Skill::getId).toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package school.faang.user_service.exception.goal;

public class GoalNotExistException extends RuntimeException {

public GoalNotExistException(long goalId) {
super("Goal with id %d not exist".formatted(goalId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package school.faang.user_service.exception.goal;

public class MaxActiveGoalPerUserException extends RuntimeException {

public MaxActiveGoalPerUserException(Long userId, int goalLimit) {
super("User with id %s reach max goals active limit of %d".formatted(userId, goalLimit));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package school.faang.user_service.exception.goal;

public class UpdateComleteGoalException extends RuntimeException {

public UpdateComleteGoalException(long goalId) {
super("Goal with id %d is complete. Update not allowed.".formatted(goalId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package school.faang.user_service.exception.goal;

public class UpdateGoalWithActiveSubGoalsException extends RuntimeException {

public UpdateGoalWithActiveSubGoalsException(long goalId, String activeSubGoalsId) {
super("Goal with id %d have active sub goals [%s]. Update not allowed.".formatted(goalId, activeSubGoalsId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package school.faang.user_service.exception.skill;

public class AddedSkillNotExistException extends RuntimeException {

public AddedSkillNotExistException(String skillsId) {
super("Contains skills [%s] that not exist!!".formatted(skillsId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ public interface SkillRepository extends JpaRepository<Skill, Long> {
@Modifying
void assignSkillToUser(long skillId, long userId);

@Query(nativeQuery = true, value = "INSERT INTO goal_skill (goal_id, skill_id) VALUES (:goalId, :skillId)")
@Modifying
void assignSkillToGoal(long goalId, long skillId);

@Query(value = "DELETE FROM goal_skill WHERE goal_id = :goalId", nativeQuery = true)
@Modifying
void removeSkillsFromGoal(Long goalId);

@Query(nativeQuery = true, value = """
SELECT s.* FROM skill s
WHERE s.id IN (SELECT gs.skill_id FROM goal_skill gs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package school.faang.user_service.repository.goal;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import school.faang.user_service.entity.User;
import school.faang.user_service.entity.goal.Goal;

import java.util.List;
Expand All @@ -23,6 +23,13 @@ INSERT INTO goal (title, description, parent_goal_id, status, created_at, update
""")
Goal create(String title, String description, Long parent);

@Modifying
@Query(nativeQuery = true, value = """
INSERT INTO user_goal (user_Id, goal_Id, created_at, updated_at)
VALUES (:userId, :goalId, NOW(), NOW())
""")
void assignGoalToUser(long userId, long goalId);

@Query(nativeQuery = true, value = """
SELECT COUNT(ug.goal_id) FROM user_goal ug
JOIN goal g ON g.id = ug.goal_id
Expand All @@ -46,5 +53,5 @@ WITH RECURSIVE subtasks AS (
JOIN user_goal ug ON u.id = ug.user_id
WHERE ug.goal_id = :goalId
""")
List<User> findUsersByGoalId(long goalId);
List<Long> findUsersByGoalId(long goalId);
}
88 changes: 88 additions & 0 deletions src/main/java/school/faang/user_service/service/GoalService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package school.faang.user_service.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import school.faang.user_service.config.context.UserContext;
import school.faang.user_service.entity.Skill;
import school.faang.user_service.entity.goal.Goal;
import school.faang.user_service.entity.goal.GoalStatus;
import school.faang.user_service.exception.goal.GoalNotExistException;
import school.faang.user_service.repository.SkillRepository;
import school.faang.user_service.repository.goal.GoalRepository;
import school.faang.user_service.validator.goal.GoalValidator;
import school.faang.user_service.validator.goal.SkillValidator;

import java.util.List;

@Service
@RequiredArgsConstructor
public class GoalService {

private final GoalRepository goalRepository;
private final SkillRepository skillRepository;

private final UserContext userContext;

private final GoalValidator goalValidator;
private final SkillValidator skillValidator;

private final SkillService skillService;

@Transactional
public Goal createGoal(Goal newGoalData, List<Long> skillsId, Long parentId) {
long userId = userContext.getUserId();
goalValidator.validateMaxActiveGoalLimit(goalRepository.countActiveGoalsPerUser(userId));

if (parentId != null) {
goalRepository.findById(parentId).orElseThrow(() -> new GoalNotExistException(parentId));
}
Goal newGoal = goalRepository.create(newGoalData.getTitle(), newGoalData.getDescription(), parentId);
goalRepository.assignGoalToUser(userId, newGoal.getId());

if (!skillsId.isEmpty()) {
skillService.assignSkillToGoal(newGoal.getId(), skillsId);
}
return newGoal;
}

@Transactional
public Goal update(long goalId, Goal newGoalData, List<Long> skillsId) {
Goal dbGoal = goalRepository.findById(goalId).orElseThrow(() -> new GoalNotExistException(goalId));
goalValidator.validateUpdateCompleteGoal(dbGoal);
skillValidator.validateExistingSkills(
skillsId.stream()
.filter(skillId -> !skillRepository.existsById(skillId))
.toList()
);

dbGoal.setTitle(newGoalData.getTitle());
dbGoal.setDescription(newGoalData.getDescription());
dbGoal.setStatus(newGoalData.getStatus());

boolean skillsChanged = !dbGoal.getSkillsToAchieve()
.stream()
.map(Skill::getId)
.toList()
.equals(skillsId);

if (skillsChanged) {

skillService.updateSkillForGoal(goalId, skillsId);
dbGoal.setSkillsToAchieve(skillRepository.findSkillsByGoalId(goalId));
}

if (dbGoal.getStatus().equals(GoalStatus.COMPLETED)) {
List<Long> involvedUsersId = goalRepository.findUsersByGoalId(dbGoal.getId());

goalValidator.validateAllSubGoalsCompleted(goalId, goalRepository.findByParent(goalId));

involvedUsersId.forEach(userId ->
skillService.assignSkillsToUser(userId, dbGoal.getSkillsToAchieve()));
}

goalRepository.save(dbGoal);

return dbGoal;
}
}
Loading