added bootstar, font awsome, and the project is in a useable state, bit needs some manual setup.
This commit is contained in:
409
ProjectKiln/api/workflow.php
Normal file
409
ProjectKiln/api/workflow.php
Normal file
@@ -0,0 +1,409 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../db.php';
|
||||
require_once __DIR__ . '/../auth.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
function jsonResponse(array $data, int $status = 200): void
|
||||
{
|
||||
http_response_code($status);
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
header('Pragma: no-cache');
|
||||
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function getInputData(): array
|
||||
{
|
||||
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
|
||||
if (str_contains($contentType, 'application/json')) {
|
||||
$raw = file_get_contents('php://input');
|
||||
$data = json_decode($raw, true);
|
||||
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
return $_POST;
|
||||
}
|
||||
|
||||
function normalizeColor(mixed $color): string
|
||||
{
|
||||
$color = trim((string)$color);
|
||||
|
||||
if ($color === '') {
|
||||
return '#4ea1ff';
|
||||
}
|
||||
|
||||
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $color)) {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Color must be a hex color like #4ea1ff.'
|
||||
], 400);
|
||||
}
|
||||
|
||||
return strtolower($color);
|
||||
}
|
||||
|
||||
function requireState(int $stateId, string $label = 'State'): void
|
||||
{
|
||||
if ($stateId <= 0) {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'error' => "{$label} must be valid."
|
||||
], 400);
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('SELECT id FROM task_states WHERE id = ? LIMIT 1');
|
||||
$stmt->execute([$stateId]);
|
||||
|
||||
if (!$stmt->fetch()) {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'error' => "{$label} not found."
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
function requireTaskType(int $taskTypeId): void
|
||||
{
|
||||
if ($taskTypeId <= 0) {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Task type must be valid.'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('SELECT id FROM task_types WHERE id = ? LIMIT 1');
|
||||
$stmt->execute([$taskTypeId]);
|
||||
|
||||
if (!$stmt->fetch()) {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Task type not found.'
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
function optionResponse(array $option): array
|
||||
{
|
||||
return [
|
||||
'id' => (int)$option['id'],
|
||||
'name' => $option['name'],
|
||||
'default_state' => isset($option['default_state']) && $option['default_state'] !== null
|
||||
? (int)$option['default_state']
|
||||
: null,
|
||||
'icon' => $option['logo'] !== null
|
||||
? 'data:image/svg+xml;base64,' . base64_encode($option['logo'])
|
||||
: null
|
||||
];
|
||||
}
|
||||
|
||||
function workflowData(): array
|
||||
{
|
||||
$states = array_map(static function (array $state): array {
|
||||
return [
|
||||
'id' => (int)$state['id'],
|
||||
'name' => $state['name'],
|
||||
'color' => $state['color']
|
||||
];
|
||||
}, db()->query(
|
||||
'SELECT id, name, color
|
||||
FROM task_states
|
||||
ORDER BY id ASC'
|
||||
)->fetchAll());
|
||||
|
||||
$transitions = array_map(static function (array $transition): array {
|
||||
return [
|
||||
'id' => (int)$transition['id'],
|
||||
'from_id' => (int)$transition['from_id'],
|
||||
'to_id' => (int)$transition['to_id'],
|
||||
'action_name' => $transition['action_name']
|
||||
];
|
||||
}, db()->query(
|
||||
'SELECT id, from_id, to_id, action_name
|
||||
FROM task_state_transitions
|
||||
ORDER BY id ASC'
|
||||
)->fetchAll());
|
||||
|
||||
$taskTypes = array_map('optionResponse', db()->query(
|
||||
'SELECT id, name, logo, default_state
|
||||
FROM task_types
|
||||
ORDER BY id ASC'
|
||||
)->fetchAll());
|
||||
|
||||
$assignments = array_map(static function (array $assignment): array {
|
||||
return [
|
||||
'id' => (int)$assignment['id'],
|
||||
'task_type' => (int)$assignment['task_type'],
|
||||
'state' => (int)$assignment['state']
|
||||
];
|
||||
}, db()->query(
|
||||
'SELECT id, task_type, state
|
||||
FROM assigned_task_states
|
||||
ORDER BY task_type ASC, state ASC, id ASC'
|
||||
)->fetchAll());
|
||||
|
||||
return [
|
||||
'states' => $states,
|
||||
'transitions' => $transitions,
|
||||
'task_types' => $taskTypes,
|
||||
'assignments' => $assignments
|
||||
];
|
||||
}
|
||||
|
||||
$user = requireApiAuth();
|
||||
requireUserRight((int)$user['id'], 'Admin');
|
||||
|
||||
$api = $_GET['api'] ?? '';
|
||||
|
||||
switch ($api) {
|
||||
case 'WorkflowData': {
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'workflow' => workflowData()
|
||||
]);
|
||||
}
|
||||
|
||||
case 'CreateState': {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['success' => false, 'error' => 'CreateState requires POST.'], 405);
|
||||
}
|
||||
|
||||
$data = getInputData();
|
||||
$name = trim((string)($data['name'] ?? ''));
|
||||
$color = normalizeColor($data['color'] ?? '');
|
||||
|
||||
if ($name === '') {
|
||||
jsonResponse(['success' => false, 'error' => 'State name is required.'], 400);
|
||||
}
|
||||
|
||||
if (strlen($name) > 128) {
|
||||
jsonResponse(['success' => false, 'error' => 'State name is too long.'], 400);
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('INSERT INTO task_states (name, color) VALUES (?, ?)');
|
||||
$stmt->execute([$name, $color]);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'state_id' => (int)db()->lastInsertId()
|
||||
], 201);
|
||||
}
|
||||
|
||||
case 'DeleteState': {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['success' => false, 'error' => 'DeleteState requires POST.'], 405);
|
||||
}
|
||||
|
||||
$stateId = (int)($_GET['state_id'] ?? 0);
|
||||
requireState($stateId);
|
||||
|
||||
try {
|
||||
db()->beginTransaction();
|
||||
db()->prepare('DELETE FROM assigned_task_states WHERE state = ?')->execute([$stateId]);
|
||||
db()->prepare('DELETE FROM task_state_transitions WHERE from_id = ? OR to_id = ?')->execute([$stateId, $stateId]);
|
||||
db()->prepare('UPDATE task_types SET default_state = NULL WHERE default_state = ?')->execute([$stateId]);
|
||||
db()->prepare('DELETE FROM task_states WHERE id = ?')->execute([$stateId]);
|
||||
db()->commit();
|
||||
|
||||
} catch (Throwable $e) {
|
||||
db()->rollBack();
|
||||
jsonResponse(['success' => false, 'error' => 'Could not delete state.'], 500);
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'state_id' => $stateId]);
|
||||
}
|
||||
|
||||
case 'CreateTransition': {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['success' => false, 'error' => 'CreateTransition requires POST.'], 405);
|
||||
}
|
||||
|
||||
$data = getInputData();
|
||||
$fromId = (int)($data['from_id'] ?? 0);
|
||||
$toId = (int)($data['to_id'] ?? 0);
|
||||
$actionName = trim((string)($data['action_name'] ?? ''));
|
||||
|
||||
requireState($fromId, 'From state');
|
||||
requireState($toId, 'To state');
|
||||
|
||||
if ($actionName === '') {
|
||||
jsonResponse(['success' => false, 'error' => 'Action name is required.'], 400);
|
||||
}
|
||||
|
||||
if (strlen($actionName) > 128) {
|
||||
jsonResponse(['success' => false, 'error' => 'Action name is too long.'], 400);
|
||||
}
|
||||
|
||||
$stmt = db()->prepare(
|
||||
'INSERT INTO task_state_transitions (from_id, to_id, action_name)
|
||||
VALUES (?, ?, ?)'
|
||||
);
|
||||
$stmt->execute([$fromId, $toId, $actionName]);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'transition_id' => (int)db()->lastInsertId()
|
||||
], 201);
|
||||
}
|
||||
|
||||
case 'DeleteTransition': {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['success' => false, 'error' => 'DeleteTransition requires POST.'], 405);
|
||||
}
|
||||
|
||||
$transitionId = (int)($_GET['transition_id'] ?? 0);
|
||||
|
||||
if ($transitionId <= 0) {
|
||||
jsonResponse(['success' => false, 'error' => 'Transition must be valid.'], 400);
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('DELETE FROM task_state_transitions WHERE id = ?');
|
||||
$stmt->execute([$transitionId]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
jsonResponse(['success' => false, 'error' => 'Transition not found.'], 404);
|
||||
}
|
||||
|
||||
jsonResponse(['success' => true, 'transition_id' => $transitionId]);
|
||||
}
|
||||
|
||||
case 'AssignState': {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['success' => false, 'error' => 'AssignState requires POST.'], 405);
|
||||
}
|
||||
|
||||
$data = getInputData();
|
||||
$taskTypeId = (int)($data['task_type'] ?? 0);
|
||||
$stateId = (int)($data['state'] ?? 0);
|
||||
|
||||
requireTaskType($taskTypeId);
|
||||
requireState($stateId);
|
||||
|
||||
$existingStmt = db()->prepare(
|
||||
'SELECT id
|
||||
FROM assigned_task_states
|
||||
WHERE task_type = ?
|
||||
AND state = ?
|
||||
LIMIT 1'
|
||||
);
|
||||
$existingStmt->execute([$taskTypeId, $stateId]);
|
||||
$assignmentId = $existingStmt->fetchColumn();
|
||||
|
||||
if (!$assignmentId) {
|
||||
$stmt = db()->prepare(
|
||||
'INSERT INTO assigned_task_states (task_type, state)
|
||||
VALUES (?, ?)'
|
||||
);
|
||||
$stmt->execute([$taskTypeId, $stateId]);
|
||||
$assignmentId = db()->lastInsertId();
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'assignment_id' => (int)$assignmentId
|
||||
], 201);
|
||||
}
|
||||
|
||||
case 'SetDefaultState': {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['success' => false, 'error' => 'SetDefaultState requires POST.'], 405);
|
||||
}
|
||||
|
||||
$data = getInputData();
|
||||
$taskTypeId = (int)($data['task_type'] ?? 0);
|
||||
$stateId = isset($data['state']) && $data['state'] !== ''
|
||||
? (int)$data['state']
|
||||
: null;
|
||||
|
||||
requireTaskType($taskTypeId);
|
||||
|
||||
if ($stateId !== null) {
|
||||
requireState($stateId);
|
||||
|
||||
$assignedStmt = db()->prepare(
|
||||
'SELECT id
|
||||
FROM assigned_task_states
|
||||
WHERE task_type = ?
|
||||
AND state = ?
|
||||
LIMIT 1'
|
||||
);
|
||||
$assignedStmt->execute([$taskTypeId, $stateId]);
|
||||
|
||||
if (!$assignedStmt->fetch()) {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Default state must be assigned to this task type.'
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = db()->prepare(
|
||||
'UPDATE task_types
|
||||
SET default_state = ?
|
||||
WHERE id = ?'
|
||||
);
|
||||
$stmt->execute([$stateId, $taskTypeId]);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'task_type' => $taskTypeId,
|
||||
'default_state' => $stateId
|
||||
]);
|
||||
}
|
||||
|
||||
case 'RemoveAssignedState': {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['success' => false, 'error' => 'RemoveAssignedState requires POST.'], 405);
|
||||
}
|
||||
|
||||
$assignmentId = (int)($_GET['assignment_id'] ?? 0);
|
||||
|
||||
if ($assignmentId <= 0) {
|
||||
jsonResponse(['success' => false, 'error' => 'Assignment must be valid.'], 400);
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('DELETE FROM assigned_task_states WHERE id = ?');
|
||||
$lookupStmt = db()->prepare(
|
||||
'SELECT task_type, state
|
||||
FROM assigned_task_states
|
||||
WHERE id = ?
|
||||
LIMIT 1'
|
||||
);
|
||||
$lookupStmt->execute([$assignmentId]);
|
||||
$assignment = $lookupStmt->fetch();
|
||||
|
||||
if (!$assignment) {
|
||||
jsonResponse(['success' => false, 'error' => 'Assignment not found.'], 404);
|
||||
}
|
||||
|
||||
$stmt->execute([$assignmentId]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
jsonResponse(['success' => false, 'error' => 'Assignment not found.'], 404);
|
||||
}
|
||||
|
||||
db()->prepare(
|
||||
'UPDATE task_types
|
||||
SET default_state = NULL
|
||||
WHERE id = ?
|
||||
AND default_state = ?'
|
||||
)->execute([
|
||||
(int)$assignment['task_type'],
|
||||
(int)$assignment['state']
|
||||
]);
|
||||
|
||||
jsonResponse(['success' => true, 'assignment_id' => $assignmentId]);
|
||||
}
|
||||
|
||||
default: {
|
||||
jsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Unknown API action.'
|
||||
], 404);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user