Open a TaskStream board when a project is created through the API
`taskstream: true` on POST /v1/projects also creates the project in TaskStream, public, as the caller (their Pireon token is passed on), and the answer carries `taskstream_key` and `taskstream_url`. The key is `taskstream_key` or one made from the default-language slug. The board is opened before the project is saved: if TaskStream refuses it, nothing is created here and the reason comes back as a validation error (502 when TaskStream does not answer). Machine tokens need the server's own TASKSTREAM_TOKEN. Updates ignore the flag, and duplicates do not inherit the link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
88
app/Services/TaskStream/TaskStreamClient.php
Normal file
88
app/Services/TaskStream/TaskStreamClient.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\TaskStream;
|
||||
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
/**
|
||||
* The few calls this application makes to TaskStream's REST API.
|
||||
*
|
||||
* Every call is made as somebody: TaskStream accepts the same Pireon tokens
|
||||
* this application does, and whoever's token it is becomes the owner of
|
||||
* what is created.
|
||||
*/
|
||||
class TaskStreamClient
|
||||
{
|
||||
public function __construct(
|
||||
protected string $apiUrl,
|
||||
protected string $webUrl,
|
||||
protected ?string $serviceToken = null,
|
||||
protected int $timeout = 10,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $config
|
||||
*/
|
||||
public static function fromConfig(array $config): self
|
||||
{
|
||||
return new self(
|
||||
apiUrl: rtrim((string) ($config['api_url'] ?? ''), '/'),
|
||||
webUrl: rtrim((string) ($config['web_url'] ?? ''), '/'),
|
||||
serviceToken: filled($config['token'] ?? null) ? $config['token'] : null,
|
||||
timeout: (int) ($config['timeout'] ?? 10),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The token for requests that do not carry a Pireon token of their own.
|
||||
*/
|
||||
public function serviceToken(): ?string
|
||||
{
|
||||
return $this->serviceToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: string, key: string}
|
||||
*
|
||||
* @throws TaskStreamException
|
||||
*/
|
||||
public function createProject(string $token, string $key, string $name, ?string $description, bool $public): array
|
||||
{
|
||||
try {
|
||||
$response = Http::withToken($token)
|
||||
->acceptJson()
|
||||
->timeout($this->timeout)
|
||||
->post("{$this->apiUrl}/api/projects", array_filter([
|
||||
'key' => $key,
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'isPublic' => $public,
|
||||
], fn ($value) => $value !== null));
|
||||
} catch (ConnectionException $e) {
|
||||
throw TaskStreamException::unreachable($this->apiUrl, $e);
|
||||
}
|
||||
|
||||
if ($response->failed()) {
|
||||
// Nest answers `message` as a string, or as a list from validation.
|
||||
$message = $response->json('message');
|
||||
|
||||
throw new TaskStreamException(
|
||||
is_array($message) ? implode(' ', $message) : (string) ($message ?: $response->reason()),
|
||||
status: $response->status(),
|
||||
);
|
||||
}
|
||||
|
||||
return ['id' => (string) $response->json('id'), 'key' => (string) $response->json('key')];
|
||||
}
|
||||
|
||||
/**
|
||||
* Where people open a project's board.
|
||||
*/
|
||||
public function projectUrl(string $key): string
|
||||
{
|
||||
return "{$this->webUrl}/p/{$key}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user