`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>
76 lines
2.9 KiB
PHP
76 lines
2.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Resources;
|
|
|
|
use App\Models\Project;
|
|
use App\Services\TaskStream\TaskStreamClient;
|
|
use App\Support\ImagePayload;
|
|
use App\Support\TranslatablePayload;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Resources\Json\JsonResource;
|
|
|
|
/**
|
|
* @mixin Project
|
|
*/
|
|
class ProjectResource extends JsonResource
|
|
{
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function toArray(Request $request): array
|
|
{
|
|
$locale = $request->query(config('locales.query_parameter')) ?? app()->getLocale();
|
|
$resolved = TranslatablePayload::resolve($this->resource, $this->translatable, $locale);
|
|
|
|
return [
|
|
'id' => $this->id,
|
|
'locale' => $locale,
|
|
|
|
...$resolved,
|
|
|
|
'status' => $this->status->value,
|
|
'is_featured' => (bool) $this->is_featured,
|
|
'needs_help' => (bool) $this->needs_help,
|
|
'demo_url' => $this->demo_url,
|
|
'taskstream_key' => $this->taskstream_key,
|
|
'taskstream_url' => $this->taskstream_key !== null
|
|
? app(TaskStreamClient::class)->projectUrl($this->taskstream_key)
|
|
: null,
|
|
// The stored path, which is what a client PATCHes back; what was
|
|
// derived from it travels alongside, as on a page.
|
|
'cover_image' => $this->cover_image,
|
|
'cover_image_media' => ImagePayload::make(
|
|
$this->cover_image,
|
|
$this->resource->relationLoaded('coverMedia') ? $this->resource->getRelation('coverMedia') : null,
|
|
$resolved['cover_image_alt'] ?? null,
|
|
),
|
|
'started_at' => $this->started_at?->toDateString(),
|
|
'ended_at' => $this->ended_at?->toDateString(),
|
|
'published_at' => $this->published_at?->toIso8601String(),
|
|
'unpublish_at' => $this->unpublish_at?->toIso8601String(),
|
|
'position' => $this->position,
|
|
|
|
'client_id' => $this->client_id,
|
|
'lead_member_id' => $this->lead_member_id,
|
|
'technology_ids' => $this->whenLoaded('technologies', fn () => $this->technologies->pluck('id')->values()),
|
|
'service_ids' => $this->whenLoaded('services', fn () => $this->services->pluck('id')->values()),
|
|
'members' => $this->whenLoaded('members', fn () => $this->members->map(fn ($member) => [
|
|
'id' => $member->id,
|
|
'name' => $member->name,
|
|
'role' => $member->pivot->role,
|
|
])->values()),
|
|
|
|
'created_at' => $this->created_at?->toIso8601String(),
|
|
'updated_at' => $this->updated_at?->toIso8601String(),
|
|
'deleted_at' => $this->deleted_at?->toIso8601String(),
|
|
|
|
'translations' => $this->when(
|
|
in_array('translations', explode(',', (string) $request->query('with')), true),
|
|
fn () => TranslatablePayload::all($this->resource, $this->translatable),
|
|
),
|
|
];
|
|
}
|
|
}
|