BudgetController.java

package no.ntnu.idi.stud.savingsapp.controller.budget;

import io.swagger.v3.oas.annotations.Operation;
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 java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import no.ntnu.idi.stud.savingsapp.dto.budget.BudgetRequestDTO;
import no.ntnu.idi.stud.savingsapp.dto.budget.BudgetResponseDTO;
import no.ntnu.idi.stud.savingsapp.dto.budget.ExpenseRequestDTO;
import no.ntnu.idi.stud.savingsapp.dto.budget.ExpenseResponseDTO;
import no.ntnu.idi.stud.savingsapp.model.budget.Budget;
import no.ntnu.idi.stud.savingsapp.model.budget.Expense;
import no.ntnu.idi.stud.savingsapp.security.AuthIdentity;
import no.ntnu.idi.stud.savingsapp.service.BudgetService;
import no.ntnu.idi.stud.savingsapp.service.UserService;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.CrossOrigin;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Controller handling budget related requests.
 */
@CrossOrigin
@RestController
@Validated
@RequestMapping("/api/budget")
@EnableAutoConfiguration
@Tag(name = "Budget")
@Slf4j
public class BudgetController {

	@Autowired
	private BudgetService budgetService;

	@Autowired
	private UserService userService;

	@Autowired
	private ModelMapper modelMapper;

	/**
	 * Retrieves all the budgets belonging to authenticated user.
	 * @param identity The security context of the authenticated user.
	 * @return ResponseEntity containing the list of BudgetResponseDTO of the
	 * authenticated user.
	 * @apiNote This endpoint is used to fetch all the budget's related to the
	 * authenticated user. It uses the user's ID stored in the authentication principal to
	 * filter for budgets.
	 */
	@Operation(summary = "Get the list of budgets",
			description = "Get all budgets related to " + "the authenticated user")
	@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully got budgets") })
	@GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<List<BudgetResponseDTO>> getBudgetsByUser(@AuthenticationPrincipal AuthIdentity identity) {
		List<Budget> budgets = budgetService.findBudgetsByUserId(identity.getId());
		List<BudgetResponseDTO> budgetDTOs = new ArrayList<>();
		for (Budget budget : budgets) {
			budgetDTOs.add(modelMapper.map(budget, BudgetResponseDTO.class));
			log.info("[BudgetController:getBudgetsByUser] budget: {}", budget.getId());
		}
		Collections.reverse(budgetDTOs);
		return ResponseEntity.ok(budgetDTOs);
	}

	/**
	 * Retrieves a budget by its id.
	 * @param budgetId The id of the budget to be retrieved.
	 * @return ResponseEntity containing the BudgetResponseDTO.
	 */
	@Operation(summary = "Get the budget", description = "Get budget by its id ")
	@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully got budget"),
			@ApiResponse(responseCode = "500", description = "Budget is not found"), })
	@GetMapping(value = "/{budgetId}", produces = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<BudgetResponseDTO> getBudget(@PathVariable long budgetId) {
		Budget budget = budgetService.findBudgetById(budgetId);
		BudgetResponseDTO response = modelMapper.map(budget, BudgetResponseDTO.class);
		log.info("[BudgetController:getBudget] budget: {}", response.getId());
		return ResponseEntity.ok(response);
	}

	/**
	 * Creates a new budget .
	 * @param identity The security context of the authenticated user.
	 * @param request The budget request.
	 * @return ResponseEntity.
	 */
	@Operation(summary = "Create a new budget", description = "Create a new budget with based on the budget request")
	@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully created new budget") })
	@PostMapping(value = "/create", produces = MediaType.APPLICATION_JSON_VALUE,
			consumes = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<?> createBudget(@AuthenticationPrincipal AuthIdentity identity,
			@RequestBody BudgetRequestDTO request) {
		Budget budget = modelMapper.map(request, Budget.class);
		budget.setUser(userService.findById(identity.getId()));
		budget.setCreatedAt(Timestamp.from(Instant.now()));
		budgetService.createBudget(budget);
		log.info("[BudgetController:createBudget] budget created: {}", budget.getId());
		return ResponseEntity.ok().build();
	}

	/**
	 * Updates a budget.
	 * @param budgetId The budget's id.
	 * @param request The budget request.
	 * @return ResponseEntity.
	 */
	@Operation(summary = "Updates a budget", description = "Updates a budget based on the budget request")
	@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully updated budget"),
			@ApiResponse(responseCode = "500", description = "Budget is not found") })
	@PostMapping(value = "/update/{budgetId}", produces = MediaType.APPLICATION_JSON_VALUE,
			consumes = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<?> updateBudget(@PathVariable Long budgetId, @RequestBody BudgetRequestDTO request) {
		Budget budget = budgetService.findBudgetById(budgetId);
		budget.setBudgetName(request.getBudgetName());
		budget.setBudgetAmount(request.getBudgetAmount());
		budget.setExpenseAmount(request.getExpenseAmount());
		budgetService.updateBudget(budget);
		log.info("[BudgetController:updateBudget] budget updated: {}", budget.getId());
		return ResponseEntity.ok().build();
	}

	/**
	 * Deletes a budget.
	 * @param budgetId The budget's id.
	 * @return ResponseEntity.
	 */
	@Operation(summary = "Deletes a budget", description = "Deletes a budget based on provided budget id")
	@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully deleted budget"),
			@ApiResponse(responseCode = "500", description = "Budget is not found") })
	@GetMapping(value = "/delete/{budgetId}", produces = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<?> deleteBudget(@PathVariable long budgetId) {
		budgetService.deleteBudgetById(budgetId);
		log.info("[BudgetController:deleteBudget] budget deleted, id: {}", budgetId);
		return ResponseEntity.ok().build();
	}

	/**
	 * Creates/Updates an expense.
	 * @param budgetId The budget id to the budget where the expense is stored.
	 * @return ResponseEntity.
	 */
	@Operation(summary = "Created/Updates an expense",
			description = "Creates/Updates a budget based on the budget request")
	@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully updated budget"),
			@ApiResponse(responseCode = "500", description = "Budget is not found"),
			@ApiResponse(responseCode = "500", description = "Error updating expense") })
	@PostMapping(value = "/update/expense/{budgetId}", produces = MediaType.APPLICATION_JSON_VALUE,
			consumes = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<?> updateExpense(@PathVariable Long budgetId, @RequestBody ExpenseRequestDTO request) {
		Budget budget = budgetService.findBudgetById(budgetId);
		Expense expense = modelMapper.map(request, Expense.class);
		expense.setBudget(budget);
		budgetService.createExpense(expense);
		log.info("[BudgetController:updateExpense] expense updated: {}", expense.getId());
		return ResponseEntity.ok(request);
	}

	/**
	 * Retrieves an expense by its id.
	 * @param expenseId The id of the expense to be retrieved.
	 * @return ResponseEntity containing the ExpenseResponseDTO.
	 */
	@Operation(summary = "Get the expense", description = "Get expense by its id ")
	@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully got expense"),
			@ApiResponse(responseCode = "500", description = "Expense is not found"), })
	@GetMapping(value = "/expense/{expenseId}", produces = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<ExpenseResponseDTO> getExpense(@PathVariable Long expenseId) {
		Expense expense = budgetService.findExpenseById(expenseId);
		ExpenseResponseDTO response = modelMapper.map(expense, ExpenseResponseDTO.class);
		log.info("[BudgetController:getExpense] expense: {}", response.getExpenseId());
		return ResponseEntity.ok(response);
	}

	/**
	 * Retrieves all expenses belonging to a budget.
	 * @param budgetId The id of the budget where the expenses are stored.
	 * @return ResponseEntity containing the list of ExpenseResponseDTO of the budget.
	 */
	@Operation(summary = "Get the list of budgets",
			description = "Get all budgets related to " + "the authenticated user")
	@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully got expenses") })
	@GetMapping(value = "/expenses/{budgetId}", produces = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<List<ExpenseResponseDTO>> getExpenses(@PathVariable Long budgetId) {
		List<Expense> expenses = budgetService.findExpensesByBudgetId(budgetId);
		List<ExpenseResponseDTO> expenseDTOs = new ArrayList<>();
		log.info("[BudgetController:getExpenses] budget: {}", budgetId);
		for (Expense expense : expenses) {
			expenseDTOs.add(modelMapper.map(expense, ExpenseResponseDTO.class));
			log.info("[BudgetController:getExpenses] expense: {}", expense.getId());
		}
		Collections.reverse(expenseDTOs);
		return ResponseEntity.ok(expenseDTOs);
	}

	/**
	 * Deletes an expense.
	 * @param expenseId The expense's id.
	 * @return ResponseEntity.
	 */
	@Operation(summary = "Deletes an expense", description = "Deletes an expense based on provided expense id")
	@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully deleted expense"),
			@ApiResponse(responseCode = "500", description = "Expense is not found") })
	@GetMapping(value = "/delete/expense/{expenseId}", produces = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<?> deleteExpense(@PathVariable Long expenseId) {
		budgetService.deleteExpenseById(expenseId);
		log.info("[BudgetController:deleteExpense] expense deleted, id: {}", expenseId);
		return ResponseEntity.ok().build();
	}

}