PatchJobController.java

package com.wilzwert.myjobs.infrastructure.api.rest.controller;


import com.wilzwert.myjobs.core.domain.model.job.JobId;
import com.wilzwert.myjobs.core.domain.model.job.command.*;
import com.wilzwert.myjobs.core.domain.model.job.ports.driving.*;
import com.wilzwert.myjobs.infrastructure.api.rest.dto.job.*;
import com.wilzwert.myjobs.infrastructure.mapper.UpdateJobMapper;
import com.wilzwert.myjobs.infrastructure.persistence.mongo.mapper.JobMapper;
import com.wilzwert.myjobs.infrastructure.security.service.UserDetailsImpl;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
 * @author Wilhelm Zwertvaegher
 */
@RestController
@Slf4j
@RequestMapping("/api/jobs")
public class PatchJobController {

    private final UpdateJobDtoFactory updateJobDtoFactory;
    private final UpdateJobUseCase updateJobUseCase;
    private final UpdateJobStatusUseCase updateJobStatusUseCase;
    private final UpdateJobRatingUseCase updateJobRatingUseCase;
    private final JobMapper jobMapper;
    private final UpdateJobMapper updateJobMapper;
    private final Validator validator;

    public PatchJobController(
            UpdateJobDtoFactory updateJobDtoFactory,
            UpdateJobUseCase updateJobUseCase,
            UpdateJobStatusUseCase updateJobStatusUseCase,
            UpdateJobRatingUseCase updateJobRatingUseCase,
            JobMapper jobMapper,
            UpdateJobMapper updateJobMapper,
            Validator validator) {
        this.updateJobDtoFactory = updateJobDtoFactory;
        this.updateJobUseCase = updateJobUseCase;
        this.updateJobStatusUseCase = updateJobStatusUseCase;
        this.updateJobRatingUseCase = updateJobRatingUseCase;
        this.jobMapper = jobMapper;
        this.updateJobMapper = updateJobMapper;
        this.validator = validator;
    }

    @PatchMapping("/{id}")
    public JobResponse patch(@PathVariable("id") String id, @RequestBody Map<String, Object> fields, Authentication authentication) {
        // we need the right DTO for the right job
        // the factory will build one based on the request contents
        UpdateJobDto updateJobDto = updateJobDtoFactory.createUpdateJobDto(fields);
        Set<ConstraintViolation<UpdateJobDto>> violations = validator.validate(updateJobDto);
        if (!violations.isEmpty()) {
            throw new ConstraintViolationException(violations);
        }

        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        // DTO is built and validated, now the domain needs a command,
        // so we use the mapper to convert our DTO to the right command
        UpdateJobCommand command = updateJobMapper.toCommand(updateJobDto, new JobId(UUID.fromString(id)), userDetails.getId());
        log.info("got this command {}", command);

        // let's call the right usecase / method
        // TODO : we're breaking open/closed principle here, maybe we should externalize the strategy selection and execution
        // for now this will do because sealed interfaces ensure all cases are covered
        // and it is very explicit, and will remain stable
        return jobMapper.toResponse(switch (command) {
            case UpdateJobFieldCommand c -> updateJobUseCase.updateJobField(c);
            case UpdateJobRatingCommand c -> updateJobRatingUseCase.updateJobRating(c);
            case UpdateJobStatusCommand c -> updateJobStatusUseCase.updateJobStatus(c);
            case UpdateJobFullCommand c -> updateJobUseCase.updateJob(c);
        });
    }
}