Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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,83 @@
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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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 org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import school.faang.user_service.entity.filter.GoalFilterDto;
import school.faang.user_service.entity.goal.Goal;
import school.faang.user_service.entity.goal.dto.GoalDto;
import school.faang.user_service.entity.goal.mapper.GoalMapper;
import school.faang.user_service.service.GoalService;

import java.net.URI;
import java.util.List;

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

private final GoalService goalService;
private final GoalMapper goalMapper;

@GetMapping("/{goalId}")
public ResponseEntity<GoalDto> getGoal(@PathVariable long goalId) {
Goal goal = goalService.getGoalById(goalId);
return ResponseEntity.ok(goalMapper.toGoalDto(goal));
}

@GetMapping
public ResponseEntity<List<GoalDto>> getGoals(@RequestBody GoalFilterDto goalFilterDto) {
List<Goal> filteredGoals = goalService.getGoalsByFilter(goalFilterDto);
return ResponseEntity.ok(goalMapper.toGoalDtoList(filteredGoals));
}

@GetMapping("/{parentId}/subGoals")
public ResponseEntity<List<GoalDto>> getSubGoals(@PathVariable long parentId, @RequestBody GoalFilterDto goalFilterDto) {
List<Goal> filteredSubGoals = goalService.getSubGoalsByFilter(parentId, goalFilterDto);
return ResponseEntity.ok(goalMapper.toGoalDtoList(filteredSubGoals));
}

@PostMapping
public ResponseEntity<GoalDto> createGoal(@RequestBody @Valid GoalDto goalDto) {
Goal createdGoal = goalService.createGoal(
goalMapper.toGoal(goalDto),
goalDto.skillsId(),
goalDto.parentId()
);

URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(createdGoal.getId())
.toUri();

return ResponseEntity.created(location)
.body(goalMapper.toGoalDto(createdGoal));
}

@PutMapping("/{goalId}")
public ResponseEntity<GoalDto> updateGoal(@PathVariable long goalId, @RequestBody @Valid GoalDto goalDto) {
Goal createdGoal = goalService.update(
goalId,
goalMapper.toGoal(goalDto),
goalDto.skillsId()
);
return ResponseEntity.ok(goalMapper.toGoalDto(createdGoal));
}

@DeleteMapping("/{goalId}")
public ResponseEntity<Void> deleteGoal(@PathVariable long goalId) {
goalService.delete(goalId);
return ResponseEntity.ok().build();
}
}
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) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package school.faang.user_service.entity.filter;

import com.fasterxml.jackson.annotation.JsonInclude;
import school.faang.user_service.entity.goal.GoalStatus;

import java.util.List;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record GoalFilterDto(
List<Long> usersId,
String title,
GoalStatus status,
List<Long> skillsId
) {
}
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 jakarta.validation.constraints.NotBlank;
import school.faang.user_service.entity.goal.GoalStatus;

import java.util.List;

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

import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
import school.faang.user_service.entity.Skill;
import school.faang.user_service.entity.goal.Goal;
import school.faang.user_service.entity.goal.dto.GoalDto;

import java.util.List;

@Mapper(componentModel = "spring")
public interface GoalMapper {

@Mapping(source = "parent.id", target = "parentId")
@Mapping(target = "skillsId", expression = "java(mapSkillsToIds(goal.getSkillsToAchieve()))")
GoalDto toGoalDto(Goal goal);

@Mapping(target = "parent", ignore = true)
@Mapping(target = "skillsToAchieve", ignore = true)
@Mapping(target = "deadline", ignore = true)
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "updatedAt", ignore = true)
@Mapping(target = "mentor", ignore = true)
@Mapping(target = "invitations", ignore = true)
@Mapping(target = "users", ignore = true)
Goal toGoal(GoalDto goalDto);

@Mapping(target = "id", ignore = true)
@Mapping(target = "parent", ignore = true)
@Mapping(target = "skillsToAchieve", ignore = true)
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void update(@MappingTarget Goal goal, Goal goalData);

default List<Long> mapSkillsToIds(List<Skill> skills) {
return skills.stream()
.map(Skill::getId)
.toList();
}

List<GoalDto> toGoalDtoList(List<Goal> goals);

List<Goal> toGoalList(List<GoalDto> goalsDto);
}
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.goal;

public class UserNotGoalOwnerException extends RuntimeException {

public UserNotGoalOwnerException(long userId, long goalId) {
super("User with id %d not the owner of goal with id %d".formatted(userId, goalId));
}
}
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));
}
}
8 changes: 8 additions & 0 deletions src/main/java/school/faang/user_service/filter/Filter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package school.faang.user_service.filter;

public interface Filter<T, P> {

boolean isApplicable(T filterDto);

P apply(P filteredData, T filterDto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package school.faang.user_service.filter.goal;

import school.faang.user_service.entity.filter.GoalFilterDto;
import school.faang.user_service.entity.goal.Goal;
import school.faang.user_service.filter.Filter;

import java.util.stream.Stream;

public interface GoalFilter extends Filter<GoalFilterDto, Stream<Goal>> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package school.faang.user_service.filter.goal;

import org.springframework.stereotype.Component;
import school.faang.user_service.entity.filter.GoalFilterDto;
import school.faang.user_service.entity.goal.Goal;

import java.util.Objects;
import java.util.stream.Stream;

@Component
public class GoalFilterBySkills implements GoalFilter {

@Override
public boolean isApplicable(GoalFilterDto filterDto) {
return Objects.nonNull(filterDto.skillsId()) && !filterDto.skillsId().isEmpty();
}

@Override
public Stream<Goal> apply(Stream<Goal> filteredData, GoalFilterDto filterDto) {
return filteredData.filter(goal ->
goal.getSkillsToAchieve()
.stream()

Choose a reason for hiding this comment

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

.

.anyMatch(skill -> filterDto.skillsId().contains(skill.getId()))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package school.faang.user_service.filter.goal;

import org.springframework.stereotype.Component;
import school.faang.user_service.entity.filter.GoalFilterDto;
import school.faang.user_service.entity.goal.Goal;

import java.util.Objects;
import java.util.stream.Stream;

@Component
public class GoalFilterByStatus implements GoalFilter {

@Override
public boolean isApplicable(GoalFilterDto filterDto) {
return Objects.nonNull(filterDto.status());
}

@Override
public Stream<Goal> apply(Stream<Goal> filteredData, GoalFilterDto filterDto) {
return filteredData.filter(goal -> goal.getStatus().equals(filterDto.status()));

Choose a reason for hiding this comment

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

goal.getStatus() == null -> NPE

Choose a reason for hiding this comment

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

сто раз просил использовать Objects.equals

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

import org.springframework.stereotype.Component;
import school.faang.user_service.entity.filter.GoalFilterDto;
import school.faang.user_service.entity.goal.Goal;

import java.util.Objects;
import java.util.stream.Stream;

@Component
public class GoalFilterByTitle implements GoalFilter {

@Override
public boolean isApplicable(GoalFilterDto filterDto) {
return Objects.nonNull(filterDto.title());
}

@Override
public Stream<Goal> apply(Stream<Goal> filteredData, GoalFilterDto filterDto) {
return filteredData.filter(goal -> goal.getTitle()
.toLowerCase()

Choose a reason for hiding this comment

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

.

.contains(filterDto.title().toLowerCase()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package school.faang.user_service.filter.goal;

import org.springframework.stereotype.Component;
import school.faang.user_service.entity.filter.GoalFilterDto;
import school.faang.user_service.entity.goal.Goal;

import java.util.Objects;
import java.util.stream.Stream;

@Component
public class GoalFilterByUsers implements GoalFilter {

@Override
public boolean isApplicable(GoalFilterDto filterDto) {
return Objects.nonNull(filterDto.usersId()) && !filterDto.usersId().isEmpty();
}

@Override
public Stream<Goal> apply(Stream<Goal> filteredData, GoalFilterDto filterDto) {
return filteredData.filter(goal ->
goal.getUsers()
.stream()

Choose a reason for hiding this comment

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

.

.anyMatch(user -> filterDto.usersId().contains(user.getId()))
);
}
}
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
Loading