first commit

This commit is contained in:
Miguel Nogueira 2025-04-13 19:03:41 +01:00
commit 9afba6461d
30 changed files with 2864 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.env
vendor/*

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

15
.idea/dataSources.xml generated Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="tasklist@100.71.118.27" uuid="85ecee04-f3a3-4e8a-aaaa-5fb0275b3e95">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://100.71.118.27:3306/tasklist</jdbc-url>
<jdbc-additional-properties>
<property name="database.introspection.mysql.dbe5060" value="true" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

14
.idea/deployment.xml generated Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="Webvoke Customer Portal">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

7
.idea/discord.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

8
.idea/laravel-idea.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="InertiaPackage">
<option name="directoryPaths">
<list />
</option>
</component>
</project>

12
.idea/material_theme_project_new.xml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="993512f:1923a7b499f:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tasklist-api.iml" filepath="$PROJECT_DIR$/.idea/tasklist-api.iml" />
</modules>
</component>
</project>

73
.idea/php.xml generated Normal file
View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpCodeSniffer">
<phpcs_settings>
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="e39e8ac2-c813-4ffd-9483-4fc00d471116" timeout="30000" />
</phpcs_settings>
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/carbondate/carbon" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php80" />
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/psr/clock" />
<path value="$PROJECT_DIR$/vendor/nesbot/carbon" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/carbonphp/carbon-doctrine-types" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php83" />
<path value="$PROJECT_DIR$/vendor/symfony/clock" />
<path value="$PROJECT_DIR$/vendor/vlucas/phpdotenv" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/vendor/phpoption/phpoption" />
<path value="$PROJECT_DIR$/vendor/graham-campbell/result-type" />
<path value="$PROJECT_DIR$/vendor/nikic/fast-route" />
<path value="$PROJECT_DIR$/vendor/slim/slim" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/psr/http-server-middleware" />
<path value="$PROJECT_DIR$/vendor/psr/http-server-handler" />
<path value="$PROJECT_DIR$/vendor/laminas/laminas-diactoros" />
<path value="$PROJECT_DIR$/vendor/laravel/serializable-closure" />
<path value="$PROJECT_DIR$/vendor/php-di/invoker" />
<path value="$PROJECT_DIR$/vendor/php-di/php-di" />
<path value="$PROJECT_DIR$/vendor/php-di/slim-bridge" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.2" />
<component name="PhpStan">
<PhpStan_settings>
<phpstan_by_interpreter asDefaultInterpreter="true" interpreter_id="e39e8ac2-c813-4ffd-9483-4fc00d471116" timeout="60000" />
</PhpStan_settings>
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
</phpunit_settings>
</component>
<component name="Psalm">
<Psalm_settings>
<psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="e39e8ac2-c813-4ffd-9483-4fc00d471116" timeout="60000" />
</Psalm_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

10
.idea/phpspec.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPSpec">
<suites>
<PhpSpecSuiteConfiguration>
<option name="myPath" value="$PROJECT_DIR$" />
</PhpSpecSuiteConfiguration>
</suites>
</component>
</project>

6
.idea/sqldialects.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="PROJECT" dialect="MariaDB" />
</component>
</project>

46
.idea/tasklist-api.iml generated Normal file
View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/Config" isTestSource="false" packagePrefix="Config\" />
<sourceFolder url="file://$MODULE_DIR$/src/Database" isTestSource="false" packagePrefix="Database\" />
<sourceFolder url="file://$MODULE_DIR$/src/Interfaces" isTestSource="false" packagePrefix="Interfaces\" />
<sourceFolder url="file://$MODULE_DIR$/src/Models" isTestSource="false" packagePrefix="Models\" />
<sourceFolder url="file://$MODULE_DIR$/src/Repositories" isTestSource="false" packagePrefix="Repositories\" />
<sourceFolder url="file://$MODULE_DIR$/src/Utils" isTestSource="false" packagePrefix="Utils\" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/Controllers" isTestSource="false" packagePrefix="Controllers\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/carbondate/carbon" />
<excludeFolder url="file://$MODULE_DIR$/vendor/carbonphp/carbon-doctrine-types" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/graham-campbell/result-type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nesbot/carbon" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpoption/phpoption" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/clock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/clock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php83" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/vlucas/phpdotenv" />
<excludeFolder url="file://$MODULE_DIR$/vendor/laminas/laminas-diactoros" />
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/serializable-closure" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/fast-route" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/php-di" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/slim-bridge" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-factory" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-server-handler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-server-middleware" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/vendor/slim/slim" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

22
composer.json Normal file
View File

@ -0,0 +1,22 @@
{
"require": {
"ext-json": "*",
"nesbot/carbon": "*",
"vlucas/phpdotenv": "^5.6",
"ext-pdo": "*",
"slim/slim": "4.*",
"laminas/laminas-diactoros": "^3.5",
"php-di/slim-bridge": "^3.4"
},
"autoload": {
"psr-4": {
"Models\\": "src/Models/",
"Interfaces\\": "src/Interfaces",
"Database\\": "src/Database",
"Config\\": "src/Config",
"Repositories\\": "src/Repositories",
"Utils\\": "src/Utils",
"Controllers\\": "src/Controllers"
}
}
}

1904
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

18
index.php Normal file
View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
use Controllers\TaskController;
use DI\Bridge\Slim\Bridge;
use DI\Container;
use Controllers\HomeFrontController;
require_once __DIR__ . '/vendor/autoload.php';
$container = new Container();
$app = Bridge::create($container);
$app->addBodyParsingMiddleware();
$app->get('/', [HomeFrontController::class, 'home']);
$app->post('/tasks', [TaskController::class, 'addTask']);
$app->run();

46
src/Config/Config.php Normal file
View File

@ -0,0 +1,46 @@
<?php declare(strict_types=1);
namespace Config;
use Dotenv\Dotenv;
class Config
{
private Dotenv $env;
public function __construct()
{
$this->env = Dotenv::createImmutable(dirname(__DIR__, 2));
$this->env->load();
}
public function getDbHost(): string {
return $_ENV['DB_HOST'] ?? 'localhost';
}
public function getDbUser(): string {
return $_ENV['DB_USER'] ?? 'root';
}
public function getDbPassword(): string {
return $_ENV['DB_PASSWORD'];
}
public function getDbName(): string {
return $_ENV['DB_NAME'];
}
public function getDbCharset(): string {
return $_ENV['DB_CHARSET'] ?? 'utf8mb4';
}
public function getDbCollation(): string {
return $_ENV['DB_COLLATION'] ?? 'utf8mb4_unicode_ci';
}
public function getDbPort(): int
{
return (int) $_ENV['DB_PORT'] ?? 3306;
}
}

View File

@ -0,0 +1,46 @@
<?php declare(strict_types=1);
namespace Controllers;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Utils\StructuredResponseBuilder;
class HomeFrontController
{
private $container;
private $builder;
/**
* @param StructuredResponseBuilder $builder Response Builder interface.
*/
public function __construct(StructuredResponseBuilder $builder)
{
$this->builder = $builder;
}
public function home(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$builderResponse = $this->builder
->setError()
->setErrorMessage("This is an API and is therefore not meant to handle requests with your browser.")
->setErrorCode(400)
->setPayload([])
->build();
// ResponseInterface is immutable
$newResponse = $response;
$newResponse->withStatus(400, "Bad Request")->withHeader('Content-Type', 'application/json');
$newResponse->getBody()->write($builderResponse);
return $newResponse;
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace Controllers;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use Exceptions\InvalidTaskDateException;
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);
$errorResponse = $response->withStatus(400, 'Invalid payload');
$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);
$errorResponse = $response->withStatus('400', 'Missing arguments');
$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)) {
throw new \DomainException('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);
$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).');
$errorResponse = $response->withStatus(400);
$errorResponse->getBody()->write($this->builder->build());
return $errorResponse;
}
$task = new Task();
$task->setTitle($params['tasks']['name'])
->setStatus(TaskStatus::tryFrom((int) $params['tasks']['status']))
->setDescription($params['tasks']['description'])
->setStartDt($params['tasks']['start'])
->setEndDt($params['tasks']['end']);
if (!$this->repository->create($task))
{
$this->builder->setError()
->setErrorMessage('An unexpected error has occurred whilst trying to perform this operation.')
->setErrorCode(500);
$errorResponse = $response->withStatus(500);
$errorResponse->getBody()->write($this->builder->build());
return $errorResponse;
}
$createdResponse = $response->withStatus(201);
$this->builder->setOptionalMessage('Task created.')
->setPayload($task->persist());
$createdResponse->getBody()->write($this->builder->build());
return $createdResponse;
}
}

View File

@ -0,0 +1,56 @@
<?php declare(strict_types=1);
namespace Database;
use Config\Config;
use Exceptions\DatabaseConnectionException;
use PDO;
class Connection
{
protected PDO $conn;
private Config $configManager;
/**
* @throws DatabaseConnectionException
*/
public function __construct()
{
$this->configManager = new Config();
$this->connect();
}
private function configure(): string
{
$hostname = $this->configManager->getDbHost();
$dbName = $this->configManager->getDbName();
$port = $this->configManager->getDbPort();
return "mysql:host=$hostname;port=$port;dbname=$dbName";
}
/**
* @throws DatabaseConnectionException Thrown when the connection fails.
*/
public function connect(): void
{
$username = $this->configManager->getDbUser();
$password = $this->configManager->getDbPassword();
try {
$this->conn = new \PDO($this->configure(), $username, $password);
} catch (\PDOException $e) {
throw new DatabaseConnectionException("A connection to the underlying backend could not be established. Please try again later", $e->getCode(), $e->getPrevious());
}
}
public function getConnection(): PDO
{
return $this->conn;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Exceptions;
class DatabaseConnectionException extends \Exception
{
public function __construct(string $message = "Database connection error", int $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Exceptions;
class InvalidTaskDateException extends \Exception
{
public function __construct(string $message = "Invalid task date.", int $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Exceptions;
class TaskNotFoundException extends \Exception
{
}

8
src/Interfaces/Model.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace Interfaces;
interface Model
{
}

View File

@ -0,0 +1,23 @@
<?php
namespace Interfaces;
use Models\Task;
interface TaskDao
{
public function create(Task $task): bool;
/**
* Returns an array of Tasks
* @return array
*/
public function readAll(): array;
public function readById($id): Task;
public function update(Task $task): bool;
public function delete(Task $task): bool;
}

View File

@ -0,0 +1,8 @@
<?php
namespace Interfaces;
interface Validator
{
public static function validate(): bool;
}

124
src/Models/Task.php Normal file
View File

@ -0,0 +1,124 @@
<?php declare(strict_types=1);
namespace Models;
use Carbon\Carbon;
use Utils\Enums\TaskStatus;
class Task
{
private int $id;
private string $title;
private string $description;
private TaskStatus $status;
private string $endDt;
private string $startDt;
public function __construct($status = TaskStatus::STARTED)
{
$this->status = $status;
}
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getDescription(): string
{
return $this->description;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function getStartDt(): string
{
return $this->startDt;
}
public function setStartDt(string $startDt): self
{
$this->startDt = Carbon::parse($startDt)->toDateTimeString();
return $this;
}
public function getEndDt(): string
{
return $this->endDt;
}
public function setEndDt(string $endDt): self
{
$this->endDt = Carbon::parse($endDt)->toDateTimeString();
return $this;
}
public function getStatus(): TaskStatus
{
return $this->status;
}
public function setStatus(TaskStatus $status): self
{
$this->status = $status;
return $this;
}
public function getTaskId(): int
{
return $this->id;
}
public static function fromArray($taskData): Task
{
$task = new self();
$task->id = $taskData['id'] ?? null;
$task->title = $taskData['name'] ?? '';
$task->description = $taskData['description'] ?? '';
$task->status = $taskData['status'] ?? 'started';
$task->startDt = $taskData['start'] ?? '';
$task->endDt = $taskData['end'] ?? '';
return $task;
}
/**
* Returns the array representation of the database entity.
* @return array
*/
public function persist(): array
{
return [
'task_owner' => 1, // fake hard-coded user for now
'created_at' => Carbon::now()->toDateTimeString(),
'updated_at' => Carbon::now()->toDateTimeString(),
'name' => $this->getTitle(),
'description' => $this->getDescription(),
'start' => $this->getStartDt(),
'end' => $this->getEndDt(),
'status_id' => $this->getStatus()->value
];
}
}

View File

@ -0,0 +1,79 @@
<?php declare(strict_types=1);
namespace Repositories;
use Carbon\Carbon;
use Database\Connection;
use Interfaces\TaskDao;
use Models\Task;
use PDO;
class TaskRepository implements TaskDao
{
private PDO $conn;
public function __construct(Connection $connection)
{
$this->conn = $connection->getConnection();
}
public function create(Task $task): bool
{
$data = $task->persist();
$stmt = $this->conn->prepare(
"INSERT INTO Tasks (task_owner, created_at, updated_at, name, description, start, end, status_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
);
$stmt->bindParam(1, $data['task_owner']);
$stmt->bindParam(2, $data['created_at']);
$stmt->bindParam(3, $data['updated_at']);
$stmt->bindParam(4, $data['name']);
$stmt->bindParam(5, $data['description']);
$stmt->bindParam(6, $data['start']);
$stmt->bindParam(7, $data['end']);
$stmt->bindParam(8, $data['status_id']);
return $stmt->execute();
}
public function readAll(): array
{
$stmt = $this->conn->query('SELECT * FROM Tasks');
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function readById($id): Task
{
$stmt = $this->conn->prepare('SELECT * FROM Tasks WHERE id = ?');
$stmt->execute([$id]);
return Task::fromArray($stmt->fetch(PDO::FETCH_ASSOC));
}
public function update(Task $task): bool
{
$stmt = $this->conn->prepare(
"UPDATE Tasks SET task_owner = ?, updated_at = ?, name = ?, description = ?, start = ?, end = ?, status_id = 0 WHERE id = ?"
);
return $stmt->execute([
1, // Mock user ID for now, we haven't implemented authentication yet
Carbon::now(),
$task->getTitle(),
$task->getDescription(),
$task->getStartDt(),
$task->getEndDt(),
$task->getStatus(),
$task->getTaskId()
]);
}
public function delete(Task $task): bool
{
$stmt = $this->conn->prepare('DELETE FROM Tasks WHERE id = ?');
return $stmt->execute([$task->getTaskId()]);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Utils\Enums;
enum TaskStatus: int
{
/**
* Used when a task has just been started.
*/
case STARTED = 1;
/**
* Used when a task is currently being worked on.
*/
case IN_PROGRESS = 2;
/**
* Used when a task is currently blocking this task.
*/
case BLOCKED = 3;
/**
* Used when a task is currently completed.
*/
case COMPLETED = 4;
}

View File

@ -0,0 +1,134 @@
<?php declare(strict_types=1);
namespace Utils;
class StructuredResponseBuilder
{
/**
* @var bool If true, the response payload is automatically formated as an error
*/
private bool $isErrorResponse = false;
/**
* @var int Only used if isErrorResponse is True. Sets the HTTP response code for the error.
*/
private int $isErrorResponseCode;
/**
* @var string Only used if isErrorResponse is True. Sets the HTTP error message for the error.
*/
private string $isErrorMessage;
/**
* @var string Sets the optional message to be sent within the payload.
*/
private string $optionalMessage;
/**
* @var array Sets the payload to be sent within the response.
*/
private array $payload = [];
/**
* @return bool
*/
public function isError(): bool
{
return $this->isErrorResponse;
}
/**
* @param bool $isErrorResponse
* @return $this
*/
public function setError(bool $isErrorResponse = true): self
{
$this->isErrorResponse = $isErrorResponse;
return $this;
}
/**
* @return int
*/
public function getErrorCode(): int
{
return $this->isErrorResponseCode;
}
/**
* @param int $isErrorResponseCode
* @return $this
*/
public function setErrorCode(int $isErrorResponseCode): self
{
$this->isErrorResponseCode = $isErrorResponseCode;
return $this;
}
/**
* @return string
*/
public function getErrorMessage(): string
{
return $this->isErrorMessage;
}
/**
* @param string $isErrorMessage
* @return $this
*/
public function setErrorMessage(string $isErrorMessage): self
{
$this->isErrorMessage = $isErrorMessage;
return $this;
}
/**
* @return string
*/
public function getOptionalMessage(): string
{
return $this->optionalMessage;
}
/**
* @param string $optionalMessage
* @return $this
*/
public function setOptionalMessage(string $optionalMessage): self
{
$this->optionalMessage = $optionalMessage;
return $this;
}
/**
* @return array
*/
public function getPayload(): array
{
return $this->payload;
}
/**
* @param array $payload
* @return $this
*/
public function setPayload(array $payload): self
{
$this->payload = $payload;
return $this;
}
public function build(): string
{
return json_encode([
"error" => $this->isError(),
"code" => (!$this->isError()) ? 'NaN' : $this->getErrorCode(),
"message" => ($this->isError()) ? $this->getErrorMessage() : $this->getOptionalMessage(),
"payload" => $this->getPayload()
]);
}
}