GoalController.java
package no.ntnu.idi.stud.savingsapp.controller.goal;
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 io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import no.ntnu.idi.stud.savingsapp.dto.goal.*;
import no.ntnu.idi.stud.savingsapp.exception.ExceptionResponse;
import no.ntnu.idi.stud.savingsapp.model.goal.Challenge;
import no.ntnu.idi.stud.savingsapp.model.goal.Goal;
import no.ntnu.idi.stud.savingsapp.model.goal.Group;
import no.ntnu.idi.stud.savingsapp.security.AuthIdentity;
import no.ntnu.idi.stud.savingsapp.service.ChallengeService;
import no.ntnu.idi.stud.savingsapp.service.GoalService;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Controller for managing goals within the application.
*/
@RestController
@RequestMapping("/api/goals")
@EnableAutoConfiguration
@Tag(name = "Goal")
@Slf4j
public class GoalController {
@Autowired
private GoalService goalService;
@Autowired
private ChallengeService challengeService;
@Autowired
private ModelMapper modelMapper;
/**
* Creates a new goal based on the provided goal data.
* @param identity The security context of the authenticated user.
* @param request The data transfer object containing the details needed to create a
* goal.
* @return ResponseEntity containing the newly created GoalDTO and the HTTP status.
*/
@Operation(summary = "Create a goal", description = "Create a new goal")
@ApiResponses(value = { @ApiResponse(responseCode = "201", description = "Successfully created a goal") })
@ResponseStatus(HttpStatus.CREATED)
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<GoalDTO> createGoal(@AuthenticationPrincipal AuthIdentity identity,
@RequestBody CreateGoalDTO request) {
Goal createGoal = modelMapper.map(request, Goal.class);
Goal goal = goalService.createGoal(createGoal, request.getDistribution(), identity.getId());
GoalDTO goalDTO = modelMapper.map(goal, GoalDTO.class);
log.info("[GoalController:createGoal] goal: {}", goalDTO.getId());
return ResponseEntity.status(HttpStatus.CREATED).body(goalDTO);
}
/**
* Retrieves all goals associated with the authenticated user.
* @param identity The security context of the authenticated user.
* @return ResponseEntity containing a list of GoalDTOs for the user's goals and the
* HTTP status.
*/
@Operation(summary = "Get goals", description = "Get the goals of the authenticated user")
@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved the goals") })
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<GoalDTO>> getGoals(@AuthenticationPrincipal AuthIdentity identity) {
List<Goal> goals = goalService.getGoals(identity.getId());
List<GoalDTO> goalsDTO = goals.stream().map(goal -> modelMapper.map(goal, GoalDTO.class)).toList();
log.info("[GoalController:getGoals] user: {}", identity.getId());
for (GoalDTO goalDTO : goalsDTO) {
log.info("[GoalController:getGoals] goal: {}", goalDTO.getId());
}
return ResponseEntity.ok(goalsDTO);
}
/**
* Updates the progress of a specific challenge by marking a particular day as
* completed. This method allows users to record progress on challenges associated
* with their goals.
* @param identity The security context of the authenticated user, including user
* identification.
* @param request The data transfer object containing the challenge ID, the day to
* mark, and the amount saved.
* @return A ResponseEntity indicating the status of the operation.
* @apiNote This endpoint returns HTTP 202 (Accepted) on successful update, and may
* return HTTP 401 if the specified day is already marked as completed or is outside
* the allowed range. Error details are provided in the response body.
*/
@Operation(summary = "Update a challenge", description = "Update a challenge day as completed")
@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully updated the challenge"),
@ApiResponse(responseCode = "401", description = "Day is already completed or day outside of range",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))) })
@PostMapping(value = "/update-challenge", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> updateChallenge(@AuthenticationPrincipal AuthIdentity identity,
@RequestBody MarkChallengeDTO request) {
challengeService.updateProgress(identity.getId(), request.getId(), request.getDay(), request.getAmount());
log.info("[GoalController:updateChallenge] challenge: {}", request.getId());
return ResponseEntity.status(HttpStatus.ACCEPTED).build();
}
/**
* Updates the saving amount for a specific challenge. This allows users to modify the
* potential savings target for any challenge associated with their goals.
* @param identity The security context of the authenticated user.
* @param request The data transfer object containing the challenge ID and the new
* saving amount.
* @return A ResponseEntity indicating the success of the update operation.
* @apiNote This endpoint returns HTTP 202 (Accepted) on a successful update of the
* challenge amount.
*/
@Operation(summary = "Update challenge saving amount", description = "Update the challenge saving amount")
@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully updated the challenge") })
@PostMapping(value = "/update-challenge-amount", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> updateChallengeAmount(@AuthenticationPrincipal AuthIdentity identity,
@RequestBody MarkChallengeDTO request) {
challengeService.updateSavingAmount(identity.getId(), request.getId(), request.getAmount());
log.info("[GoalController:updateChallengeAmount] challenge: {}", request.getId());
return ResponseEntity.status(HttpStatus.ACCEPTED).build();
}
@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<GoalDTO> getGoal(@PathVariable Long id) {
Goal goal = goalService.getGoal(id);
GoalDTO goalDTO = modelMapper.map(goal, GoalDTO.class);
log.info("[GoalController:getGoal] goal: {}", goalDTO.getId());
return ResponseEntity.ok(goalDTO);
}
@PatchMapping(value = "/challenge/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ChallengeDTO> regenerateChallenge(@AuthenticationPrincipal AuthIdentity identity,
@PathVariable Long id) {
Challenge challenge = challengeService.regenerateChallenge(identity.getId(), id);
ChallengeDTO challengeDTO = modelMapper.map(challenge, ChallengeDTO.class);
return ResponseEntity.ok(challengeDTO);
}
@GetMapping(value = "/group/{goalId}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<GoalDTO>> getGroupInfo(@PathVariable Long goalId) {
Group group = goalService.getGroup(goalId);
List<GoalDTO> goals = group.getGoals().stream().map(g -> modelMapper.map(g, GoalDTO.class)).toList();
return ResponseEntity.ok(goals);
}
}