feature: Initial MVP

This commit is contained in:
Keith Solomon
2026-04-05 16:20:39 -05:00
parent 3af0b9cd0f
commit 812e5c2f2a
60 changed files with 5917 additions and 5 deletions
+14
View File
@@ -0,0 +1,14 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Shared API bootstrap.
*/
declare(strict_types=1);
require_once dirname(__DIR__, 2) . '/bootstrap.php';
use IronKanban\Service\BoardService;
return new BoardService();
+27
View File
@@ -0,0 +1,27 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Return the full board state for a project.
*/
declare(strict_types=1);
$boardService = include __DIR__ . '/_bootstrap.php';
use IronKanban\Support\Http;
use IronKanban\Support\HttpHalt;
try {
$projectId = trim((string) ($_GET['project'] ?? ''));
if ($projectId === '') {
Http::error('project_required', 422);
}
Http::json($boardService->getBoardState($projectId));
} catch (HttpHalt $halt) {
return;
} catch (Throwable $exception) {
Http::error($exception->getMessage(), 500);
}
+35
View File
@@ -0,0 +1,35 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Create a new board column.
*/
declare(strict_types=1);
$boardService = include __DIR__ . '/_bootstrap.php';
use IronKanban\Support\Http;
use IronKanban\Support\HttpHalt;
try {
Http::requireMethod('POST');
Http::verifyCsrf();
$input = Http::input();
$boardService->assertRevision((string) $input['projectId'], isset($input['revision']) ? (string) $input['revision'] : null);
Http::json(
[
'success' => true,
'state' => $boardService->createColumn(
(string) $input['projectId'],
trim((string) ($input['label'] ?? 'Untitled Column'))
),
]
);
} catch (HttpHalt $halt) {
return;
} catch (Throwable $exception) {
Http::error($exception->getMessage(), $exception->getMessage() === 'conflict' ? 409 : 500);
}
+37
View File
@@ -0,0 +1,37 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Create a new task.
*/
declare(strict_types=1);
$boardService = include __DIR__ . '/_bootstrap.php';
use IronKanban\Support\Http;
use IronKanban\Support\HttpHalt;
try {
Http::requireMethod('POST');
Http::verifyCsrf();
$input = Http::input();
$boardService->assertRevision((string) $input['projectId'], isset($input['revision']) ? (string) $input['revision'] : null);
Http::json(
[
'success' => true,
'state' => $boardService->createTask(
(string) $input['projectId'],
trim((string) ($input['title'] ?? 'Untitled Task')),
(string) ($input['column'] ?? 'backlog'),
(string) ($input['body'] ?? '')
),
]
);
} catch (HttpHalt $halt) {
return;
} catch (Throwable $exception) {
Http::error($exception->getMessage(), $exception->getMessage() === 'conflict' ? 409 : 500);
}
+36
View File
@@ -0,0 +1,36 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Delete or trash a task.
*/
declare(strict_types=1);
$boardService = include __DIR__ . '/_bootstrap.php';
use IronKanban\Support\Http;
use IronKanban\Support\HttpHalt;
try {
Http::requireMethod('POST');
Http::verifyCsrf();
$input = Http::input();
$boardService->assertRevision((string) $input['projectId'], isset($input['revision']) ? (string) $input['revision'] : null);
Http::json(
[
'success' => true,
'state' => $boardService->deleteTask(
(string) $input['projectId'],
(string) $input['taskId'],
(bool) ($input['permanent'] ?? false)
),
]
);
} catch (HttpHalt $halt) {
return;
} catch (Throwable $exception) {
Http::error($exception->getMessage(), $exception->getMessage() === 'conflict' ? 409 : 500);
}
+37
View File
@@ -0,0 +1,37 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Move a task between columns.
*/
declare(strict_types=1);
$boardService = include __DIR__ . '/_bootstrap.php';
use IronKanban\Support\Http;
use IronKanban\Support\HttpHalt;
try {
Http::requireMethod('POST');
Http::verifyCsrf();
$input = Http::input();
$boardService->assertRevision((string) $input['projectId'], isset($input['revision']) ? (string) $input['revision'] : null);
Http::json(
[
'success' => true,
'state' => $boardService->moveTask(
(string) $input['projectId'],
(string) $input['taskId'],
(string) $input['column'],
max(0, (int) ($input['index'] ?? 0))
),
]
);
} catch (HttpHalt $halt) {
return;
} catch (Throwable $exception) {
Http::error($exception->getMessage(), $exception->getMessage() === 'conflict' ? 409 : 500);
}
+35
View File
@@ -0,0 +1,35 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Create or update a note.
*/
declare(strict_types=1);
$boardService = include __DIR__ . '/_bootstrap.php';
use IronKanban\Support\Http;
use IronKanban\Support\HttpHalt;
try {
Http::requireMethod('POST');
Http::verifyCsrf();
$input = Http::input();
$boardService->assertRevision((string) $input['projectId'], isset($input['revision']) ? (string) $input['revision'] : null);
Http::json(
[
'success' => true,
'state' => $boardService->saveNote(
(string) $input['projectId'],
$input
),
]
);
} catch (HttpHalt $halt) {
return;
} catch (Throwable $exception) {
Http::error($exception->getMessage(), $exception->getMessage() === 'conflict' ? 409 : 500);
}
+36
View File
@@ -0,0 +1,36 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Update an existing task.
*/
declare(strict_types=1);
$boardService = include __DIR__ . '/_bootstrap.php';
use IronKanban\Support\Http;
use IronKanban\Support\HttpHalt;
try {
Http::requireMethod('POST');
Http::verifyCsrf();
$input = Http::input();
$boardService->assertRevision((string) $input['projectId'], isset($input['revision']) ? (string) $input['revision'] : null);
Http::json(
[
'success' => true,
'state' => $boardService->saveTask(
(string) $input['projectId'],
(string) $input['taskId'],
$input
),
]
);
} catch (HttpHalt $halt) {
return;
} catch (Throwable $exception) {
Http::error($exception->getMessage(), $exception->getMessage() === 'conflict' ? 409 : 500);
}
+36
View File
@@ -0,0 +1,36 @@
<?php
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
/**
* Update board column definitions.
*/
declare(strict_types=1);
$boardService = include __DIR__ . '/_bootstrap.php';
use IronKanban\Support\Http;
use IronKanban\Support\HttpHalt;
try {
Http::requireMethod('POST');
Http::verifyCsrf();
$input = Http::input();
$boardService->assertRevision((string) $input['projectId'], isset($input['revision']) ? (string) $input['revision'] : null);
Http::json(
[
'success' => true,
'state' => $boardService->updateBoard(
(string) $input['projectId'],
is_array($input['columns'] ?? null) ? $input['columns'] : [],
isset($input['deletedColumnId']) ? (string) $input['deletedColumnId'] : null
),
]
);
} catch (HttpHalt $halt) {
return;
} catch (Throwable $exception) {
Http::error($exception->getMessage(), $exception->getMessage() === 'conflict' ? 409 : 500);
}