Compare commits

..

8 Commits

Author SHA1 Message Date
423f6ae383 gitignore 2025-02-17 01:25:42 +00:00
2785a5406f commit 2025-02-17 01:24:55 +00:00
8b97d6b724 updated .gitignore 2025-02-17 01:23:48 +00:00
8d052192f7 <parsedown inline="true">will parse markdown between the tags</parsedown> 2025-02-17 01:22:54 +00:00
6e6bae7e1d initial project commit 2025-02-17 01:22:07 +00:00
68ef649451 redirect check in AuthController, which sends back to last page 2025-02-17 01:21:40 +00:00
a852dccd8b - added interface - for CRUD, ensures all methods are present and aligned.
- added trait for checking authorisation with redirect stored in session to return - shows which controllers requires auth, adds check function
2025-02-17 01:21:16 +00:00
baa051ab11 now ignoring test.php 2025-02-17 01:18:41 +00:00
16 changed files with 342 additions and 41 deletions

11
.gitignore vendored
View File

@ -1,6 +1,7 @@
/lib/
/downloads/
/app/.env.cfg
/public/tmp/
composer.lock composer.lock
/storage/ lib/
downloads/
app/.env.cfg
public/tmp/
storage/
public/test.php

View File

@ -2,11 +2,7 @@
class AttachmentController { class AttachmentController {
private function check_access($f3){ use RequiresAuth;
if(!$f3->exists('SESSION.user')){
$f3->reroute('/login');
}
}
// list attachments // list attachments
public function index($f3){ public function index($f3){

View File

@ -40,6 +40,12 @@ class AuthController {
'role' => $user['role'], 'role' => $user['role'],
'role_name' => $user['role_name'] 'role_name' => $user['role_name']
]); ]);
if($f3->exists('SESSION.redirect')){
$redirect = $f3->get('SESSION.redirect');
$f3->clear('SESSION.redirect');
$f3->reroute($redirect);
}
$f3->reroute('/dashboard'); $f3->reroute('/dashboard');
} else { } else {
@ -49,6 +55,7 @@ class AuthController {
// if here, login failed. // if here, login failed.
$f3->set('SESSION.login_error', 'Invalid username'); $f3->set('SESSION.login_error', 'Invalid username');
} }
$f3->reroute('/login'); $f3->reroute('/login');
} }

View File

@ -1,13 +1,8 @@
<?php <?php
class KBController { class KBController implements CRUD {
protected function check_access($f3){ use RequiresAuth;
if(!$f3->exists('SESSION.user')){
// $f3->set('SESSION.error', 'You don\'t have permission for this ticket.');
$f3->reroute('/login');
}
}
public function index($f3){ public function index($f3){

View File

@ -0,0 +1,29 @@
<?php
class ProjectController implements CRUD {
use RequiresAuth;
// list all projects
public function index($f3){
$this->check_access($f3);
$f3->set('content', '../ui/views/project/index.html');
echo \Template::instance()->render('../ui/templates/layout.html');
}
// create a new project
public function createForm($f3){
}
public function create($f3){
}
// show project details including links, tickets, events, tasks
public function view($f3){}
// update project details
public function editForm($f3){}
public function update($f3){}
}

View File

@ -1,12 +1,8 @@
<?php <?php
class TagController { class TagController implements CRUD {
protected function check_access($f3){ use RequiresAuth;
if(!$f3->exists('SESSION.user')){
$f3->reroute('/login');
}
}
/** /**
* List all tags * List all tags
@ -40,5 +36,20 @@ class TagController {
$db->exec('INSERT IGNORE INTO tags (name, color) VALUES (?, ?)', [$name, $color]); $db->exec('INSERT IGNORE INTO tags (name, color) VALUES (?, ?)', [$name, $color]);
$f3->reroute('/tags'); $f3->reroute('/tags');
} }
public function view($f3)
{
}
public function editForm($f3)
{
}
public function update($f3)
{
}
} }

View File

@ -1,6 +1,8 @@
<?php <?php
class TicketController { class TicketController implements CRUD {
use RequiresAuth;
// list all tickts // list all tickts
public function index($f3){ public function index($f3){
@ -22,6 +24,7 @@ class TicketController {
} }
// view a single ticket // view a single ticket
// TODO_PROJECTS: show a link back to the related project
public function view($f3){ public function view($f3){
$this->check_access($f3); $this->check_access($f3);
@ -41,6 +44,7 @@ class TicketController {
} }
// show create form // show create form
// TODO_PROJECTS: dropdown to associate ticket with project
public function createForm($f3){ public function createForm($f3){
$this->check_access($f3); $this->check_access($f3);
$f3->set('content', '../ui/views/ticket/create.html'); $f3->set('content', '../ui/views/ticket/create.html');
@ -92,6 +96,7 @@ class TicketController {
// show edit form // show edit form
// including custom forms // including custom forms
// TODO_PROJECTS: allow reasssigning or removing a project association
public function editForm($f3){ public function editForm($f3){
$this->check_access($f3); $this->check_access($f3);
@ -207,12 +212,7 @@ class TicketController {
$f3->reroute('/ticket/' . $parent_id); $f3->reroute('/ticket/' . $parent_id);
} }
protected function check_access($f3){
if(!$f3->exists('SESSION.user')){
// $f3->set('SESSION.error', 'You don\'t have permission for this ticket.');
$f3->reroute('/login');
}
}
protected function get_ticket_check_edit_permission($f3){ protected function get_ticket_check_edit_permission($f3){

View File

@ -1,16 +1,11 @@
<?php <?php
class UserController { class UserController implements CRUD {
use RequiresAuth;
// list all users (admin only) // list all users (admin only)
protected function check_access($f3){
$current_user = $f3->get('SESSION.user');
if(!$current_user || $current_user['role_name'] !== 'admin'){
$f3->reroute('/login');
}
}
public function index($f3){ public function index($f3){
$this->check_access($f3); $this->check_access($f3);
@ -59,4 +54,20 @@ class UserController {
[$new_username, $user_id]); [$new_username, $user_id]);
$f3->reroute('/users'); $f3->reroute('/users');
} }
public function createForm($f3)
{
}
public function create($f3)
{
}
public function view($f3)
{
}
} }

View File

@ -3,6 +3,21 @@
class ParsedownHelper extends \Prefab { class ParsedownHelper extends \Prefab {
static public function render($args) { static public function render($args) {
if(isset($args['@attrib']) && $args['@attrib']['inline'] === 'true'){
$return = \Parsedown::instance()->text($args[0]);
return '
<div class="content">
<parsedown_rendered>'.$return.'</parsedown_rendered>
</div>
';
}
return '<pre>'.print_r($args,1).'<pre>';
$content = $args[0]; $content = $args[0];
$content_token = \Template::instance()->token($content); $content_token = \Template::instance()->token($content);

25
app/interfaces/CRUD.php Normal file
View File

@ -0,0 +1,25 @@
<?php
interface CRUD {
// list all
public function index($f3);
// show form
public function createForm($f3);
// handle POST
public function create($f3);
// show single
public function view($f3);
// show edit form
public function editForm($f3);
// handle post
public function update($f3);
}

View File

@ -0,0 +1,13 @@
<?php
trait RequiresAuth {
public function check_access($f3){
if(!$f3->exists('SESSION.user')){
// $f3->set('SESSION.error', 'You don\'t have permission for this ticket.');
$f3->set('SESSION.redirect', $f3->get('PATH'));
$f3->reroute('/login');
}
}
}

View File

@ -84,8 +84,15 @@ $f3->route('POST /parsedown/preview', 'ParsedownPreview->view');
// dashboard // dashboard
$f3->route('GET /dashboard', 'DashboardController->index'); $f3->route('GET /dashboard', 'DashboardController->index');
// projects
$f3->route('GET /projects', 'ProjectController->index');
$f3->route('GET /project/@id', 'ProjectController->view');
$f3->route('GET /project/create', 'ProjectController->createForm');
$f3->route('POST /project/create', 'ProjectController->create');
$f3->route('GET /project/@id/edit', 'ProjectController->editForm');
$f3->route('POST /project/@id/update', 'ProjectController->update');
// additional routes // additional routes - user
$f3->route('GET /users', 'UserController->index'); $f3->route('GET /users', 'UserController->index');
$f3->route('GET /user/@id/edit', 'UserController->editForm'); $f3->route('GET /user/@id/edit', 'UserController->editForm');
$f3->route('POST /user/@id/update', 'UserController->update'); $f3->route('POST /user/@id/update', 'UserController->update');

View File

@ -1,6 +1,62 @@
<?php <?php
$password = "pass!local"; class TicketPhraseGen {
echo password_hash($password, PASSWORD_DEFAULT); private $opinion = ['amazing', 'cool', 'wicked', 'lovely'];
private $size = ['little', 'tiny', 'small', 'medium', 'large', 'huge', 'gigantic'];
private $age = ['young', 'old', 'ancient', 'new'];
private $shape = ['round', 'square', 'oval', 'long', 'short'];
private $color = ['red', 'orange', 'pink', 'blue', 'green', 'yellow', 'purple'];
private $origin = ['American', 'English', 'French', 'German', 'Spanish', 'Scottish', 'Welsh', 'Irish'];
private $material = ['wooden', 'silver', 'gold', 'plastic', 'glass'];
private $purpose = ['working', 'funny', 'serious', 'whittling'];
private $noun = ['ticket', 'device', 'gadget', 'system', 'dragon'];
public function convert_id_to_phrase(int $id): string {
$list = ['opinion', 'size', 'age', 'shape', 'color', 'origin', 'material', 'purpose', 'noun'];
$bases = [
'opinion' => count($this->opinion),
'size' => count($this->size),
'age' => count($this->age),
'shape' => count($this->shape),
'color' => count($this->color),
'origin' => count($this->origin),
'material' => count($this->material),
'purpose' => count($this->purpose),
'noun' => count($this->noun),
];
// perform mixed-radix conversion
$order = ['noun', 'purpose', 'material', 'origin', 'color', 'shape', 'age', 'size', 'opinion'];
$digits = [];
foreach($order as $category){
$radix = $bases[$category];
$digits[$category] = $id % $radix;
$id = intdiv($id, $radix);
}
if($id > 0){
throw new Exception("ID too large");
}
foreach($list as $k){
$phrase[] = $this->$k[$digits[$k]];
}
return implode('-', $phrase);
}
}
function generate(){
$generator = new TicketPhraseGen();
$ticket_number = 2;
$phrase = $generator->convert_id_to_phrase($ticket_number);
echo "Ticket #{$ticket_number} is {$phrase}";
}
generate();
function password_test(){
$password = "pass!local";
echo password_hash($password, PASSWORD_DEFAULT);
}

View File

@ -42,6 +42,7 @@
<div id="mainNavbar" class="navbar-menu"> <div id="mainNavbar" class="navbar-menu">
<div class="navbar-start"> <div class="navbar-start">
<a class="navbar-item" href="/dashboard">Dashboard</a> <a class="navbar-item" href="/dashboard">Dashboard</a>
<a class="navbar-item" href="/projects">Projects</a>
<a class="navbar-item" href="/tickets">Tickets</a> <a class="navbar-item" href="/tickets">Tickets</a>
<a class="navbar-item" href="/kb">Knowledge Base</a> <a class="navbar-item" href="/kb">Knowledge Base</a>
<a class="navbar-item" href="/tags">Tags</a> <a class="navbar-item" href="/tags">Tags</a>

134
ui/views/project/index.html Normal file
View File

@ -0,0 +1,134 @@
<h3 class="title">Projects</h3>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr class="is-primary">
<th>ID</th>
<th>Title</th>
<th>Requester</th>
<th>Created By</th>
<th>Created At</th>
<th>Start Date</th>
<th>End Date</th>
</tr>
</thead>
<tbody>
<tr>
<td>{id}</td>
<td>{title}</td>
<td>{requester}</td>
<td>{created_by}</td>
<td>{created_at}</td>
<td>{start_date}</td>
<td>{end_date}</td>
</tr>
</tbody>
</table>
<hr>
<h3 class="title">Project {NAME}</h3>
<div class="columns">
<div class="column is-two-thirds">
<h3 class="title subtitle">Overview</h3>
<div class="skeleton-lines">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div class="column">
<div class="block">
<h3 class="title subtitle">Links</h3>
<div class="skeleton-lines">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
</div>
<hr>
<div class="columns">
<div class="column">
<h3 class="title subtitle">Tickets</h3>
<div class="skeleton-lines">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div class="column">
<h3 class="title subtitle">Tasks</h3>
<div class="skeleton-lines">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div class="column">
<h3 class="title subtitle">Events</h3>
<div class="skeleton-lines">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
<hr>
<div class="columns">
<div class="column">
<h3 class="title">Timeline</h3>
<div class="skeleton-lines">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
<parsedown inline="true">
---
## View project
A central place to see everything for this project:
- Overview: (title, description, links, start/end dates).
- related tickets (with status and priorities)
- events
- tasks
- timeline combining events, tickets, milestone dates
## Example Workflow
- create a project - `team manager overview`
-- attach relevant links
- add tickets - each new request or issue can be a ticket referencing this project
- add events - quick notes about management meetings, or verbal discussions that don't need ticket overhead
-- meeting on 01 jan to discuss layout
-- teams message on 28 jan clarifying data requirements
- project tasks - for smaller to do items that don't warrant a full ticket
-- identify location of required data, create initial pq connections, build a mockup layout
## Reporting Timelines
- timeline view - merge ticket data with project_events sorted by date - chronological Overview
- status summaries - how many tickets open, on hold, completed
- progress tracking - sumarries or gantt style charts
</parsedown>

View File