Added Eloquent and Phinx for handling database connections and migrations; added a Ticket model and a simple ticket/create form to create new tickets

This commit is contained in:
Gregory Ballantine 2022-11-20 22:25:29 -05:00
parent 41aca6215e
commit 830a950bf4
16 changed files with 2073 additions and 15 deletions

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
# Composer dependencies
vendor/
# Local data
data/
# NPM dependencies (building CSS and JS)
node_modules/

View File

@ -4,15 +4,22 @@ $primary-color-highlight: darken($primary-color, 10%)
$box-shadow-1: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)
$box-shadow-2: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)
$nav-height: 60px
body
padding-top: $nav-height
background: lightgrey
.container
max-width: 1100px
padding: 20px 30px
#main-nav
position: fixed
top: 0
left: 0
width: 100%
height: 60px
height: $nav-height
background: #212121
color: #eee
font-size: 2.75rem
@ -36,3 +43,7 @@ body
&:hover
color: $primary-color-highlight
#main-wrapper
margin-top: 25px
background: white

View File

@ -20,6 +20,8 @@
"slim/psr7": "^1.6",
"php-di/php-di": "^6.4",
"slim/twig-view": "^3.3",
"hassankhan/config": "^3.0"
"hassankhan/config": "^3.0",
"illuminate/database": "^9.40",
"robmorgan/phinx": "^0.13.1"
}
}

1847
composer.lock generated

File diff suppressed because it is too large Load Diff

0
data/.gitkeep Normal file
View File

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddTicketsTable extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('tickets');
$table->addColumn('title', 'string', ['null' => false])
->addColumn('body', 'text', ['null' => false])
->addColumn('severity', 'string', ['default' => 'low'])
->addColumn('due_at', 'datetime')
->addTimestamps()
->addIndex(['title', 'body', 'due_at', 'severity'])
->create();
}
}

37
phinx.php Normal file
View File

@ -0,0 +1,37 @@
<?php
return
[
'paths' => [
'migrations' => '%%PHINX_CONFIG_DIR%%/db/migrations',
'seeds' => '%%PHINX_CONFIG_DIR%%/db/seeds'
],
'environments' => [
'default_migration_table' => 'phinxlog',
'default_environment' => 'development',
'production' => [
'adapter' => 'mysql',
'host' => 'localhost',
'name' => 'production_db',
'user' => 'root',
'pass' => '',
'port' => '3306',
'charset' => 'utf8',
],
'development' => [
'adapter' => 'sqlite',
'name' => './data/goliath',
'suffix' => '.db'
],
'testing' => [
'adapter' => 'mysql',
'host' => 'localhost',
'name' => 'testing_db',
'user' => 'root',
'pass' => '',
'port' => '3306',
'charset' => 'utf8',
]
],
'version_order' => 'creation'
];

View File

@ -5,12 +5,18 @@ namespace BitGoblin\Goliath\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig;
use BitGoblin\Goliath\Models\Ticket;
class HomeController extends Controller {
public function getIndex(Request $request, Response $response): Response {
// find tickets from the database
$tickets = Ticket::all();
$view = Twig::fromRequest($request);
return $view->render($response, 'index.twig');
return $view->render($response, 'index.twig', [
'tickets' => $tickets,
]);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace BitGoblin\Goliath\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Views\Twig;
use BitGoblin\Goliath\Models\Ticket;
class TicketController extends Controller {
public function getCreate(Request $request, Response $response): Response {
$view = Twig::fromRequest($request);
return $view->render($response, 'ticket/create.twig');
}
public function postCreate(Request $request, Response $response): Response {
$params = (array)$request->getParsedBody();
$ticket = new Ticket;
$ticket->title = $params['ticket_title'];
$ticket->body = $params['ticket_body'];
$ticket->severity = $params['ticket_severity'];
$ticket->due_at = $params['ticket_due'];
$ticket->save();
// redirect the user back to the home page
return $response
->withHeader('Location', '/')
->withStatus(302);
}
}

16
src/Models/Ticket.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace BitGoblin\Goliath\Models;
use Illuminate\Database\Eloquent\Model;
class Ticket extends Model {
protected $fillable = [
'title',
'body',
'severity',
'due_at',
];
}

View File

@ -18,10 +18,16 @@ $container->set('config', function () use ($config) {
return $config;
});
// Load database configuration
require_once __DIR__ . '/database.php';
// Set container to create App with on AppFactory
AppFactory::setContainer($container);
$app = AppFactory::create();
// Allow body parsing for POST parameters
$app->addBodyParsingMiddleware();
// Add Error Handling Middleware
$app->addErrorMiddleware(true, false, false);

10
src/database.php Normal file
View File

@ -0,0 +1,10 @@
<?php
$capsule = new \Illuminate\Database\Capsule\Manager;
$capsule->addConnection($config->get('database'));
$capsule->setAsGlobal();
$capsule->bootEloquent();
$container->set('db', function () use ($capsule) {
return $capsule;
});

View File

@ -6,3 +6,7 @@ use Slim\Views\Twig;
// index GET route - this page should welcome the user and direct them to the available actions
$app->get('/', '\\BitGoblin\\Goliath\\Controllers\\HomeController:getIndex')->setName('index');
// /ticket/create GET route - allows a user to fill out a form to create a ticket
$app->get('/ticket/create', '\\BitGoblin\\Goliath\\Controllers\\TicketController:getCreate')->setName('ticket.create');
$app->post('/ticket/create', '\\BitGoblin\\Goliath\\Controllers\\TicketController:postCreate');

View File

@ -3,5 +3,30 @@
{% block title %}Home{% endblock %}
{% block content %}
<p>This is a test.</p>
<div class="row">
<div class="columns twelve">
<h1>Welcome to Goliath!</h1>
</div>
</div>
<div class="row">
{% if tickets|length > 0 %}
<table class="columns twelve">
<thead>
<th>Title</th>
<th>Severity</th>
<th>Due date</th>
</thead>
<tbody>
{% for ticket in tickets %}
<td>{{ ticket.title }}</td>
<td>{{ ticket.severity }}</td>
<td>{{ ticket.due_at }}</td>
{% endfor %}
</tbody>
</table>
{% else %}
<p>There a no tickets in the database at this time.</p>
{% endif %}
</div>
{% endblock %}

View File

@ -15,13 +15,15 @@
<div class="nav-left">
<ul class="nav-menu">
<li>Goliath</li>
<li class="nav-link"><a href="/">Home</a></li>
<li class="nav-link"><a href="/tickets">Tickets</a></li>
<li class="nav-link"><a href="{{ url_for('index') }}">Home</a></li>
<li class="nav-link"><a href="{{ url_for('ticket.create') }}">Create</a></li>
<li class="nav-link"><a href="/search">Search</a></li>
</ul>
</div>
</nav>
<div id="main-wrapper" class="container">
{% block content %}{% endblock %}
</div>
</body>
</html>

41
views/ticket/create.twig Normal file
View File

@ -0,0 +1,41 @@
{% extends 'layout.twig' %}
{% block title %}Create new ticket{% endblock %}
{% block content %}
<div class="row">
<div class="columns twelve">
<h1>Create new ticket</h1>
</div>
</div>
<div class="row">
<div class="columns twelve">
<form action="/ticket/create" method="POST" class="u-full-width">
<div class="row">
<div class="six columns">
<label for="ticket_title">Title</label>
<input id="ticket_title" class="u-full-width" type="text" placeholder="My new ticket" name="ticket_title">
</div>
<div class="columns three">
<label for="ticket_due">Due at</label>
<input id="ticket_due" class="u-full-width" type="datetime-local" name="ticket_due">
</div>
<div class="three columns">
<label for="ticket_severity">Severity level</label>
<select id="ticket_severity" class="u-full-width" name="ticket_severity">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
</div>
<label for="ticket_body">Ticket body</label>
<textarea id="ticket_body" class="u-full-width" placeholder="Explain what this ticket is about..." name="ticket_body"></textarea>
<input class="button-primary" type="submit" value="Submit">
</form>
</div>
</div>
{% endblock %}