2025-04-13 19:03:41 +01:00
< ? php
namespace Controllers ;
use Carbon\Carbon ;
use Carbon\Exceptions\InvalidFormatException ;
use Exceptions\InvalidTaskDateException ;
2025-04-14 00:05:48 +01:00
use Exceptions\TaskNotFoundException ;
2025-04-13 19:03:41 +01:00
use Models\Task ;
use Psr\Http\Message\ResponseInterface ;
use Psr\Http\Message\ServerRequestInterface ;
use Repositories\TaskRepository ;
use Utils\Enums\TaskStatus ;
use Utils\StructuredResponseBuilder ;
class TaskController
{
private $repository , $builder ;
// instead of requesting the builder here, we could simply make it static
public function __construct ( TaskRepository $taskRepository , StructuredResponseBuilder $builder )
{
$this -> repository = $taskRepository ;
$this -> builder = $builder ;
}
// response is immutable. psr-7 impl makes copies of stuff.
// TODO: There's a LOT of validation here. We need to build a validator, use a library, or refactor this method.
public function addTask ( ServerRequestInterface $request , ResponseInterface $response ) : ResponseInterface
{
$params = $request -> getParsedBody ();
$requiredKeys = [ 'name' , 'description' , 'status' , 'start' , 'end' ];
if ( ! array_key_exists ( 'tasks' , $params )) {
$this -> builder -> setError ();
$this -> builder -> setErrorMessage ( 'Malformed request payload. Please try again.' );
$this -> builder -> setErrorCode ( 400 );
2025-04-13 20:07:07 +01:00
$errorResponse = $response -> withStatus ( 400 , 'Invalid payload' ) -> withHeader ( 'Content-Type' , 'application/json' );
2025-04-13 19:03:41 +01:00
$errorResponse -> getBody () -> write ( $this -> builder -> build ());
return $errorResponse ;
}
$missingKeys = [];
foreach ( $requiredKeys as $key )
{
if ( ! array_key_exists ( $key , $params [ 'tasks' ])) {
$missingKeys [] = $key ;
}
}
if ( ! empty ( $missingKeys )) {
$this -> builder -> setError ();
$this -> builder -> setErrorMessage ( " The following parameters are missing: " . implode ( ', ' , $missingKeys ));
$this -> builder -> setErrorCode ( 400 );
2025-04-13 20:07:07 +01:00
$errorResponse = $response -> withStatus ( '400' , 'Missing arguments' ) -> withHeader ( 'Content-Type' , 'application/json' );
2025-04-13 19:03:41 +01:00
$errorResponse -> getBody () -> write ( $this -> builder -> build ());
return $errorResponse ;
}
try
{
$start = Carbon :: parse ( $params [ 'tasks' ][ 'start' ]);
$end = Carbon :: parse ( $params [ 'tasks' ][ 'end' ]);
if ( $start -> gt ( $end ) || $end -> lt ( $start )) {
2025-04-14 00:05:48 +01:00
throw new InvalidTaskDateException ( 'Invalid task date. Start date must not be after end date.' );
2025-04-13 19:03:41 +01:00
}
} catch ( InvalidFormatException | \DomainException $exception )
{
$this -> builder -> setError ();
$this -> builder -> setErrorMessage ( $exception -> getMessage ());
$this -> builder -> setErrorCode ( 400 );
2025-04-13 20:07:07 +01:00
$errorResponse = $response -> withStatus ( 400 ) -> withHeader ( 'Content-Type' , 'application/json' );
2025-04-13 19:03:41 +01:00
$errorResponse -> getBody () -> write ( $this -> builder -> build ());
return $errorResponse ;
}
if ( is_null ( TaskStatus :: tryFrom ( $params [ 'tasks' ][ 'status' ]))) {
$this -> builder -> setError ();
$this -> builder -> setErrorCode ( 400 );
$this -> builder -> setErrorMessage ( 'Invalid task status code. Status must range from 1-4 (started, in progress, blocked, completed).' );
2025-04-13 20:07:07 +01:00
$errorResponse = $response -> withStatus ( 400 ) -> withHeader ( 'Content-Type' , 'application/json' );
2025-04-13 19:03:41 +01:00
$errorResponse -> getBody () -> write ( $this -> builder -> build ());
return $errorResponse ;
}
$task = new Task ();
2025-04-14 15:06:31 +01:00
$task -> setName ( $params [ 'tasks' ][ 'name' ])
-> setStatusId ( TaskStatus :: tryFrom (( int ) $params [ 'tasks' ][ 'status' ]))
2025-04-13 19:03:41 +01:00
-> setDescription ( $params [ 'tasks' ][ 'description' ])
2025-04-14 15:06:31 +01:00
-> setStart ( $params [ 'tasks' ][ 'start' ])
-> setEnd ( $params [ 'tasks' ][ 'end' ]);
2025-04-13 19:03:41 +01:00
2025-04-13 22:18:14 +01:00
$createdTask = $this -> repository -> create ( $task );
if ( $createdTask == 0 )
2025-04-13 19:03:41 +01:00
{
$this -> builder -> setError ()
-> setErrorMessage ( 'An unexpected error has occurred whilst trying to perform this operation.' )
-> setErrorCode ( 500 );
2025-04-13 20:07:07 +01:00
$errorResponse = $response -> withStatus ( 500 ) -> withHeader ( 'Content-Type' , 'application/json' );
2025-04-13 19:03:41 +01:00
$errorResponse -> getBody () -> write ( $this -> builder -> build ());
return $errorResponse ;
}
2025-04-13 20:07:07 +01:00
$createdResponse = $response -> withStatus ( 201 ) -> withHeader ( 'Content-Type' , 'application/json' );
2025-04-13 19:03:41 +01:00
$this -> builder -> setOptionalMessage ( 'Task created.' )
2025-04-13 22:18:14 +01:00
-> setPayload ( $task -> setId ( $createdTask ) -> persist ());
2025-04-13 19:03:41 +01:00
$createdResponse -> getBody () -> write ( $this -> builder -> build ());
return $createdResponse ;
}
2025-04-13 22:18:14 +01:00
public function getTasks ( ServerRequestInterface $request , ResponseInterface $response ) : ResponseInterface
{
$tasks = $this -> repository -> readAll ();
$payload [ 'tasks' ] = $tasks ;
$this -> builder -> setPayload ( $payload ) -> setOptionalMessage ( 'Listing all tasks: ' );
$response -> getBody () -> write ( $this -> builder -> build ());
return $response ;
}
2025-04-14 00:05:48 +01:00
public function getTask ( ServerRequestInterface $request , ResponseInterface $response , string $id ) : ResponseInterface
{
try {
$payload [ 'task' ] = $this -> repository -> readById (( int ) $id ) -> persist ();
$this -> builder -> setPayload ( $payload ) -> setOptionalMessage ( " Task retrieved. " );
$response -> getBody () -> write ( $this -> builder -> build ());
return $response ;
} catch ( TaskNotFoundException $e ) {
$this -> builder -> setError () -> setErrorMessage ( $e -> getMessage ()) -> setErrorCode ( 404 );
$errorResponse = $response -> withStatus ( 404 , $e -> getMessage ());
$response -> getBody () -> write ( $this -> builder -> build ());
return $errorResponse ;
}
}
2025-04-14 00:25:18 +01:00
public function delete ( ServerRequestInterface $request , ResponseInterface $response , $id ) : ResponseInterface
{
try
{
$task = $this -> repository -> readById (( int ) $id );
$this -> repository -> delete ( $task );
$this -> builder -> setOptionalMessage ( 'Task deleted.' );
$response -> getBody () -> write ( $this -> builder -> build ());
return $response ;
} catch ( TaskNotFoundException $exception )
{
$this -> builder -> setError () -> setErrorMessage ( $exception -> getMessage ()) -> setErrorCode ( 404 );
$errorResponse = $response -> withStatus ( 404 );
$errorResponse -> getBody () -> write ( $this -> builder -> build ());
return $errorResponse ;
}
}
2025-04-16 15:33:13 +01:00
public function update ( ServerRequestInterface $request , ResponseInterface $response , $id ) : ResponseInterface
{
$payloadWarnings = [];
$toUpdate = $request -> getParsedBody ()[ 'payload' ][ 'update' ];
$toUpdate [ 'updated_at' ] = Carbon :: now () -> toDateTimeString ();
$acceptableFields = [
'task_owner' ,
'name' ,
'description' ,
'start' ,
'end'
];
foreach ( $toUpdate as $updateField => $value )
{
if ( $updateField !== 'updated_at' && ! in_array ( $updateField , $acceptableFields ))
{
$payloadWarnings [] = " W: An invalid, unknown, or locked field has been truncated: " . $updateField ;
unset ( $toUpdate [ $updateField ]);
}
}
try
{
$task = $this -> repository -> readById ( $id );
// if we used the dirty implementation, we could skip all this code
foreach ( $toUpdate as $property => $value )
{
// ignore updated_at
if ( $property !== 'updated_at' )
{
$task -> { 'set' . ucfirst ( snakeToCamel ( $property ))}( $value );
}
}
$finalFields = array_keys ( $toUpdate );
$result = $this -> repository -> updateSelective ( $task , $finalFields );
if ( $result && empty ( $payloadWarnings ))
{
return $response -> withStatus ( 204 );
}
elseif ( $result )
{
$this -> builder -> setOptionalMessage ( 'Task updated successfully with warnings.' )
-> setPayload ([
'updated' => $toUpdate ,
'warnings' => $payloadWarnings ,
]);
$response -> getBody () -> write ( $this -> builder -> build ());
return $response ;
}
else
{
$this -> builder -> setError ()
-> setErrorMessage ( 'An unexpected error occurred while updating the resource.' )
-> setErrorCode ( 500 );
$errorResponse = $response -> withStatus ( 500 ) -> withHeader ( 'Content-Type' , 'application/json' );
$errorResponse -> getBody () -> write ( $this -> builder -> build ());
return $errorResponse ;
}
}
catch ( TaskNotFoundException $e )
{
$this -> builder -> setError ()
-> setErrorMessage ( $e -> getMessage ())
-> setErrorCode ( 404 );
$errorResponse = $response -> withStatus ( 404 ) -> withHeader ( 'Content-Type' , 'application/json' );
$errorResponse -> getBody () -> write ( $this -> builder -> build ());
return $errorResponse ;
}
}
2025-04-16 16:06:19 +01:00
public function updateStatus ( ServerRequestInterface $request , ResponseInterface $response , $id ) : ResponseInterface
{
try
{
$task = $this -> repository -> readById ( $id );
$newCode = $request -> getParsedBody ()[ 'payload' ][ 'status_id' ];
if ( is_null ( TaskStatus :: tryFrom ( $newCode )))
{
$this -> builder -> setError () -> setErrorCode ( 400 ) -> setErrorMessage ( 'Invalid task status, must be between 1 to 4 (started, in progress, blocked, completed).' );
$response -> getBody () -> write ( $this -> builder -> build ());
return $response -> withStatus ( 400 );
}
$task -> setStatusId ( TaskStatus :: from ( $newCode ));
$this -> repository -> updateSelective ( $task , [ 'status_id' ]);
return $response -> withStatus ( 204 );
} catch ( TaskNotFoundException $e )
{
$this -> builder -> setError () -> setErrorCode ( 404 ) -> setErrorMessage ( $e -> getMessage ());
$response -> getBody () -> write ( $this -> builder -> build ());
return $response -> withStatus ( 404 );
}
}
2025-04-13 19:03:41 +01:00
}