Add team task tracker API endpoints
New endpoints at api/tasks/team/ for centralized bot task tracking: - POST create.php — register a new task - POST update.php — change status, add notes (auto-sets PausedOn/CompletedOn) - GET active.php — list active/paused tasks, optional BotName filter - GET list.php — full listing with filters (BotName, Status, Channel, AssignedBy, Since) and pagination Includes schema.sql for the TeamTasks table. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cc7d6f6b4f
commit
e3933ce0c8
5 changed files with 273 additions and 0 deletions
33
api/tasks/team/active.php
Normal file
33
api/tasks/team/active.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../../helpers.php';
|
||||
|
||||
/**
|
||||
* GET /api/tasks/team/active.php
|
||||
* List all active and paused tasks.
|
||||
*
|
||||
* Optional query params: BotName (filter by bot)
|
||||
* Returns: { OK: true, Tasks: [...] }
|
||||
*/
|
||||
|
||||
$botName = trim($_GET['BotName'] ?? '');
|
||||
|
||||
try {
|
||||
$sql = "SELECT ID, BotName, Title, Status, Channel, StartedOn, PausedOn, Notes, AssignedBy
|
||||
FROM TeamTasks
|
||||
WHERE Status IN ('active', 'paused')";
|
||||
$params = [];
|
||||
|
||||
if ($botName !== '') {
|
||||
$sql .= " AND BotName = ?";
|
||||
$params[] = $botName;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY StartedOn DESC";
|
||||
|
||||
$tasks = queryTimed($sql, $params);
|
||||
|
||||
jsonResponse(['OK' => true, 'Tasks' => $tasks]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Failed to fetch tasks: ' . $e->getMessage()]);
|
||||
}
|
||||
54
api/tasks/team/create.php
Normal file
54
api/tasks/team/create.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../../helpers.php';
|
||||
|
||||
/**
|
||||
* POST /api/tasks/team/create.php
|
||||
* Register a new team task.
|
||||
*
|
||||
* Body: { BotName, Title, Channel?, Notes?, AssignedBy? }
|
||||
* Returns: { OK: true, ID: <new task ID> }
|
||||
*/
|
||||
|
||||
$data = readJsonBody();
|
||||
|
||||
$botName = trim($data['BotName'] ?? '');
|
||||
$title = trim($data['Title'] ?? '');
|
||||
$channel = trim($data['Channel'] ?? '');
|
||||
$notes = trim($data['Notes'] ?? '');
|
||||
$assignedBy = trim($data['AssignedBy'] ?? '');
|
||||
|
||||
// Validate required fields
|
||||
if ($botName === '' || $title === '') {
|
||||
http_response_code(400);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'BotName and Title are required']);
|
||||
}
|
||||
|
||||
// Length guards
|
||||
if (strlen($botName) > 50) {
|
||||
http_response_code(400);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'BotName max 50 characters']);
|
||||
}
|
||||
if (strlen($title) > 255) {
|
||||
http_response_code(400);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Title max 255 characters']);
|
||||
}
|
||||
|
||||
try {
|
||||
queryTimed("
|
||||
INSERT INTO TeamTasks (BotName, Title, Status, Channel, StartedOn, Notes, AssignedBy)
|
||||
VALUES (?, ?, 'active', ?, NOW(), ?, ?)
|
||||
", [
|
||||
$botName,
|
||||
$title,
|
||||
$channel ?: null,
|
||||
$notes ?: null,
|
||||
$assignedBy ?: null,
|
||||
]);
|
||||
|
||||
$id = (int) lastInsertId();
|
||||
|
||||
jsonResponse(['OK' => true, 'ID' => $id]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Failed to create task: ' . $e->getMessage()]);
|
||||
}
|
||||
89
api/tasks/team/list.php
Normal file
89
api/tasks/team/list.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../../helpers.php';
|
||||
|
||||
/**
|
||||
* GET /api/tasks/team/list.php
|
||||
* List all tasks with optional filters.
|
||||
*
|
||||
* Query params:
|
||||
* BotName — filter by bot name
|
||||
* Status — filter by status (active/paused/done/cancelled)
|
||||
* Channel — filter by channel
|
||||
* AssignedBy — filter by who assigned
|
||||
* Since — only tasks started on or after this date (YYYY-MM-DD)
|
||||
* Limit — max rows (default 100, max 500)
|
||||
* Offset — pagination offset (default 0)
|
||||
*
|
||||
* Returns: { OK: true, Tasks: [...], Total: <count> }
|
||||
*/
|
||||
|
||||
$botName = trim($_GET['BotName'] ?? '');
|
||||
$status = trim($_GET['Status'] ?? '');
|
||||
$channel = trim($_GET['Channel'] ?? '');
|
||||
$assignedBy = trim($_GET['AssignedBy'] ?? '');
|
||||
$since = trim($_GET['Since'] ?? '');
|
||||
$limit = min(max((int) ($_GET['Limit'] ?? 100), 1), 500);
|
||||
$offset = max((int) ($_GET['Offset'] ?? 0), 0);
|
||||
|
||||
$validStatuses = ['active', 'paused', 'done', 'cancelled'];
|
||||
|
||||
try {
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($botName !== '') {
|
||||
$where[] = "BotName = ?";
|
||||
$params[] = $botName;
|
||||
}
|
||||
|
||||
if ($status !== '') {
|
||||
if (!in_array($status, $validStatuses, true)) {
|
||||
http_response_code(400);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Invalid status filter']);
|
||||
}
|
||||
$where[] = "Status = ?";
|
||||
$params[] = $status;
|
||||
}
|
||||
|
||||
if ($channel !== '') {
|
||||
$where[] = "Channel = ?";
|
||||
$params[] = $channel;
|
||||
}
|
||||
|
||||
if ($assignedBy !== '') {
|
||||
$where[] = "AssignedBy = ?";
|
||||
$params[] = $assignedBy;
|
||||
}
|
||||
|
||||
if ($since !== '') {
|
||||
// Validate date format
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $since)) {
|
||||
http_response_code(400);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Since must be YYYY-MM-DD']);
|
||||
}
|
||||
$where[] = "StartedOn >= ?";
|
||||
$params[] = $since . ' 00:00:00';
|
||||
}
|
||||
|
||||
$whereClause = empty($where) ? '' : 'WHERE ' . implode(' AND ', $where);
|
||||
|
||||
// Get total count
|
||||
$countRow = queryOne("SELECT COUNT(*) AS Total FROM TeamTasks $whereClause", $params);
|
||||
$total = (int) ($countRow['Total'] ?? 0);
|
||||
|
||||
// Get paginated results
|
||||
$params[] = $limit;
|
||||
$params[] = $offset;
|
||||
$tasks = queryTimed("
|
||||
SELECT ID, BotName, Title, Status, Channel, StartedOn, PausedOn, CompletedOn, Notes, AssignedBy
|
||||
FROM TeamTasks
|
||||
$whereClause
|
||||
ORDER BY StartedOn DESC
|
||||
LIMIT ? OFFSET ?
|
||||
", $params);
|
||||
|
||||
jsonResponse(['OK' => true, 'Tasks' => $tasks, 'Total' => $total]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Failed to fetch tasks: ' . $e->getMessage()]);
|
||||
}
|
||||
17
api/tasks/team/schema.sql
Normal file
17
api/tasks/team/schema.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- TeamTasks: Centralized task tracker for all bots
|
||||
-- Run against payfrit_dev (and later payfrit prod)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TeamTasks (
|
||||
ID INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
BotName VARCHAR(50) NOT NULL,
|
||||
Title VARCHAR(255) NOT NULL,
|
||||
Status ENUM('active','paused','done','cancelled') NOT NULL DEFAULT 'active',
|
||||
Channel VARCHAR(100) DEFAULT NULL,
|
||||
StartedOn DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PausedOn DATETIME DEFAULT NULL,
|
||||
CompletedOn DATETIME DEFAULT NULL,
|
||||
Notes TEXT DEFAULT NULL,
|
||||
AssignedBy VARCHAR(50) DEFAULT NULL,
|
||||
INDEX idx_status (Status),
|
||||
INDEX idx_botname (BotName)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
80
api/tasks/team/update.php
Normal file
80
api/tasks/team/update.php
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../../helpers.php';
|
||||
|
||||
/**
|
||||
* POST /api/tasks/team/update.php
|
||||
* Update an existing team task (change status, add notes).
|
||||
*
|
||||
* Body: { ID, Status?, Notes?, Channel? }
|
||||
* Returns: { OK: true }
|
||||
*/
|
||||
|
||||
$data = readJsonBody();
|
||||
|
||||
$id = (int) ($data['ID'] ?? 0);
|
||||
$status = trim($data['Status'] ?? '');
|
||||
$notes = $data['Notes'] ?? null;
|
||||
$channel = $data['Channel'] ?? null;
|
||||
|
||||
if ($id <= 0) {
|
||||
http_response_code(400);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'ID is required']);
|
||||
}
|
||||
|
||||
// Validate status if provided
|
||||
$validStatuses = ['active', 'paused', 'done', 'cancelled'];
|
||||
if ($status !== '' && !in_array($status, $validStatuses, true)) {
|
||||
http_response_code(400);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Status must be one of: ' . implode(', ', $validStatuses)]);
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify task exists
|
||||
$task = queryOne("SELECT ID, Status FROM TeamTasks WHERE ID = ?", [$id]);
|
||||
if (!$task) {
|
||||
http_response_code(404);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Task not found']);
|
||||
}
|
||||
|
||||
// Build dynamic update
|
||||
$sets = [];
|
||||
$params = [];
|
||||
|
||||
if ($status !== '') {
|
||||
$sets[] = "Status = ?";
|
||||
$params[] = $status;
|
||||
|
||||
// Auto-set timestamp columns based on status transitions
|
||||
if ($status === 'paused') {
|
||||
$sets[] = "PausedOn = NOW()";
|
||||
} elseif ($status === 'done' || $status === 'cancelled') {
|
||||
$sets[] = "CompletedOn = NOW()";
|
||||
} elseif ($status === 'active' && $task['Status'] === 'paused') {
|
||||
// Resuming from pause — clear PausedOn
|
||||
$sets[] = "PausedOn = NULL";
|
||||
}
|
||||
}
|
||||
|
||||
if ($notes !== null) {
|
||||
$sets[] = "Notes = ?";
|
||||
$params[] = trim($notes);
|
||||
}
|
||||
|
||||
if ($channel !== null) {
|
||||
$sets[] = "Channel = ?";
|
||||
$params[] = trim($channel);
|
||||
}
|
||||
|
||||
if (empty($sets)) {
|
||||
http_response_code(400);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Nothing to update — provide Status, Notes, or Channel']);
|
||||
}
|
||||
|
||||
$params[] = $id;
|
||||
queryTimed("UPDATE TeamTasks SET " . implode(', ', $sets) . " WHERE ID = ?", $params);
|
||||
|
||||
jsonResponse(['OK' => true]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
apiAbort(['OK' => false, 'ERROR' => 'Failed to update task: ' . $e->getMessage()]);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue