296 lines
11 KiB
PHP
296 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Controllers;
|
|
|
|
use Carbon\Carbon;
|
|
use Carbon\Exceptions\InvalidFormatException;
|
|
use Exceptions\InvalidTaskDateException;
|
|
use Exceptions\TaskNotFoundException;
|
|
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 StructuredResponseBuilder $builder;
|
|
private TaskRepository $repository;
|
|
|
|
// 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('payload', $params)) {
|
|
$this->builder->setError();
|
|
$this->builder->setErrorMessage('Malformed request payload. Please try again.');
|
|
$this->builder->setErrorCode(400);
|
|
|
|
$errorResponse = $response->withStatus(400, 'Invalid payload')->withHeader('Content-Type', 'application/json');
|
|
$errorResponse->getBody()->write($this->builder->build());
|
|
|
|
return $errorResponse;
|
|
}
|
|
|
|
$missingKeys = [];
|
|
foreach ($requiredKeys as $key)
|
|
{
|
|
if (!array_key_exists($key, $params['payload'])) {
|
|
$missingKeys[] = $key;
|
|
}
|
|
}
|
|
|
|
if (!empty($missingKeys)) {
|
|
$this->builder->setError();
|
|
$this->builder->setErrorMessage("The following parameters are missing: " . implode(', ', $missingKeys));
|
|
$this->builder->setErrorCode(400);
|
|
|
|
$errorResponse = $response->withStatus('400', 'Missing arguments')->withHeader('Content-Type', 'application/json');
|
|
$errorResponse->getBody()->write($this->builder->build());
|
|
|
|
return $errorResponse;
|
|
}
|
|
|
|
try
|
|
{
|
|
$start = Carbon::parse($params['payload']['start']);
|
|
$end = Carbon::parse($params['payload']['end']);
|
|
|
|
if ($start->gt($end) || $end->lt($start)) {
|
|
throw new InvalidTaskDateException('Invalid task date. Start date must not be after end date.');
|
|
}
|
|
|
|
} catch (InvalidFormatException | \DomainException $exception)
|
|
{
|
|
$this->builder->setError();
|
|
$this->builder->setErrorMessage($exception->getMessage());
|
|
$this->builder->setErrorCode(400);
|
|
|
|
$errorResponse = $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
|
$errorResponse->getBody()->write($this->builder->build());
|
|
|
|
return $errorResponse;
|
|
}
|
|
|
|
if (is_null(TaskStatus::tryFrom($params['payload']['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).');
|
|
|
|
$errorResponse = $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
|
$errorResponse->getBody()->write($this->builder->build());
|
|
|
|
return $errorResponse;
|
|
}
|
|
|
|
$task = new Task();
|
|
$task->setName($params['payload']['name'])
|
|
->setStatusId(TaskStatus::tryFrom((int) $params['payload']['status']))
|
|
->setDescription($params['payload']['description'])
|
|
->setStart($params['payload']['start'])
|
|
->setEnd($params['payload']['end']);
|
|
|
|
|
|
$createdTask = $this->repository->create($task);
|
|
if ($createdTask == 0)
|
|
{
|
|
$this->builder->setError()
|
|
->setErrorMessage('An unexpected error has occurred whilst trying to perform this operation.')
|
|
->setErrorCode(500);
|
|
|
|
$errorResponse = $response->withStatus(500)->withHeader('Content-Type', 'application/json');
|
|
$errorResponse->getBody()->write($this->builder->build());
|
|
|
|
return $errorResponse;
|
|
}
|
|
|
|
$createdResponse = $response->withStatus(201)->withHeader('Content-Type', 'application/json');
|
|
$this->builder->setOptionalMessage('Task created.')
|
|
->setPayload($task->setId($createdTask)->persist());
|
|
|
|
$createdResponse->getBody()->write($this->builder->build());
|
|
return $createdResponse;
|
|
}
|
|
|
|
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->withHeader('Content-Type', 'application/json');
|
|
}
|
|
|
|
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->withHeader('Content-Type', 'application/json');
|
|
|
|
} catch (TaskNotFoundException $e) {
|
|
|
|
$this->builder->setError()->setErrorMessage($e->getMessage())->setErrorCode(404);
|
|
$errorResponse = $response->withStatus(404, $e->getMessage())->withHeader('Content-Type', 'application/json');
|
|
|
|
$response->getBody()->write($this->builder->build());
|
|
return $errorResponse;
|
|
}
|
|
}
|
|
|
|
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->withHeader('Content-Type', 'application/json');
|
|
|
|
} catch (TaskNotFoundException $exception)
|
|
{
|
|
$this->builder->setError()->setErrorMessage($exception->getMessage())->setErrorCode(404);
|
|
|
|
$errorResponse = $response->withStatus(404);
|
|
$errorResponse->getBody()->write($this->builder->build());
|
|
|
|
return $errorResponse->withHeader('Content-Type', 'application/json');
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
} |