seedproject-web/api/public/controllers/admintasks.php

131 lines
5.3 KiB
PHP
Raw Normal View History

<?php
use App\Controllers\PublicController;
/**
* Web Designer task queue. The admin submits a design brief for any page; it
* lands in cja_tasks as `queued`. The console runner (agents/console/server.mjs)
* drains the queue: claims the oldest queued row, runs the design agent, builds,
* and auto-publishes (git commit), then marks it published/failed.
*
* POST /api/admintasks/create -> { title, target_page, prompt, draft, assets }
* GET /api/admintasks/list -> [{ task_id, title, status, ... }]
* GET /api/admintasks/get?id= -> full row + result
* POST /api/admintasks/cancel -> { id } (only if still queued)
*
* All admin-gated. This controller only does CRUD on the queue; the runner owns
* execution. The Node runner claims/finishes rows via api/cli/tasks-*.php.
*/
class AdminTasks extends PublicController
{
private const STATUSES = ['queued', 'running', 'published', 'failed', 'canceled'];
public function create(): void
{
$this->requireAdmin();
$this->guardPublic('admintasks_create', 60, 300);
$in = json_decode((string) file_get_contents('php://input'), true) ?: [];
$title = mb_substr(trim((string) ($in['title'] ?? '')), 0, 200);
$target = mb_substr(trim((string) ($in['target_page'] ?? '')), 0, 200);
$prompt = trim((string) ($in['prompt'] ?? ''));
$draft = (string) ($in['draft'] ?? '');
if ($prompt === '') {
$this->json(null, 400, ['code' => 'no_prompt', 'message' => 'Describe what you want done.']);
}
if ($target === '') {
$this->json(null, 400, ['code' => 'no_target', 'message' => 'Pick a page (or a new page) for the task.']);
}
if ($title === '') {
$title = mb_substr($prompt, 0, 80);
}
// Assets: only known-safe local /media/ images and real http(s) video URLs.
$images = [];
foreach ((array) ($in['assets']['images'] ?? []) as $img) {
$url = trim((string) ($img['url'] ?? ''));
if (preg_match('#^/media/[\w./-]+$#', $url)) {
$images[] = ['url' => $url, 'alt' => mb_substr(trim((string) ($img['alt'] ?? '')), 0, 200)];
}
}
$videos = [];
foreach ((array) ($in['assets']['videos'] ?? []) as $v) {
$url = trim((string) $v);
if ($url !== '' && filter_var($url, FILTER_VALIDATE_URL) && preg_match('#^https?://#i', $url)) {
$videos[] = mb_substr($url, 0, 500);
}
}
$assets = json_encode(['images' => $images, 'videos' => $videos], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$order = (int) \Db::getValue('SELECT COALESCE(MAX(sort_order), 0) + 10 FROM cja_tasks');
\Db::insert('cja_tasks', [
'title' => $title,
'target_page' => $target,
'prompt' => $prompt,
'draft' => $draft,
'assets' => $assets,
'status' => 'queued',
'sort_order' => $order,
]);
$this->json(['ok' => true, 'id' => (int) \Db::getValue('SELECT LAST_INSERT_ID()')]);
}
public function list(): void
{
$this->requireAdmin();
$rows = \Db::select(
'SELECT task_id, title, target_page, status, result, created_at, finished_at
FROM cja_tasks ORDER BY sort_order DESC, task_id DESC LIMIT 100'
);
$out = array_map(function ($r) {
$result = json_decode($r['result'] ?? '', true) ?: [];
return [
'id' => (int) $r['task_id'],
'title' => $r['title'],
'target' => $r['target_page'],
'status' => $r['status'],
'commit' => $result['commit'] ?? '',
'summary' => $result['summary'] ?? '',
'live' => $result['live'] ?? '',
'error' => $result['error'] ?? '',
'created' => $r['created_at'],
'finished' => $r['finished_at'],
];
}, $rows);
$this->json(['tasks' => $out]);
}
public function get(): void
{
$this->requireAdmin();
$id = (int) ($_GET['id'] ?? 0);
$r = \Db::getRow('SELECT * FROM cja_tasks WHERE task_id = ?', [$id]);
if (!$r) {
$this->json(null, 404, ['code' => 'not_found', 'message' => 'No such task.']);
}
$r['assets'] = json_decode($r['assets'] ?? '{}', true) ?: [];
$r['result'] = json_decode($r['result'] ?? '{}', true) ?: [];
$this->json($r);
}
/** Cancel a task that has not started yet. */
public function cancel(): void
{
$this->requireAdmin();
$in = json_decode((string) file_get_contents('php://input'), true) ?: [];
$id = (int) ($in['id'] ?? 0);
$status = (string) \Db::getValue('SELECT status FROM cja_tasks WHERE task_id = ?', [$id]);
if ($status === '') {
$this->json(null, 404, ['code' => 'not_found', 'message' => 'No such task.']);
}
if ($status !== 'queued') {
$this->json(null, 409, ['code' => 'not_queued', 'message' => 'Only queued tasks can be canceled.']);
}
\Db::update('cja_tasks', ['status' => 'canceled', 'finished_at' => date('Y-m-d H:i:s')], 'task_id = ?', [$id]);
$this->json(['ok' => true]);
}
}