Manage projects, members and services through the API
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled
Tests / test-postgres (push) Has been cancelled
Tests / mcp (push) Has been cancelled

/api/v1/projects, /members and /services: create, list, show, update,
delete and restore, with the panel's own rules — so the MCP server can
load the collective's work, people and offer without anyone retyping
them into forms. The MCP server picks the tools up from openapi.yaml.

- Relations travel as ids and are replaced whole when sent, left alone
  when not: a project's technology_ids, service_ids and members with
  each one's role; a member's technology_ids; a service's
  technology_ids and project_ids. Record and relations are saved in
  one transaction.
- Publishing stays a permission of its own. Without projects.publish
  or services.publish, published_at, unpublish_at and is_published are
  refused — the fields the panel disables for the same people.
- MemberPolicy still lets someone edit their own profile.
- Uploads accept `for: projects` and `for: members`, landing where the
  panel's own upload fields put them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 16:07:20 +02:00
parent 6b67c7afd4
commit 82ef2b38dd
19 changed files with 1660 additions and 1 deletions

View File

@@ -5,7 +5,10 @@ declare(strict_types=1);
namespace Tests\Feature\Api;
use App\Models\Category;
use App\Models\Member;
use App\Models\Page;
use App\Models\Project;
use App\Models\Service;
use App\Models\Technology;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
@@ -66,6 +69,9 @@ class AdminApiContractTest extends TestCase
'categories' => ['path' => '/v1/categories'],
'users' => ['path' => '/v1/users'],
'technologies' => ['path' => '/v1/technologies'],
'projects' => ['path' => '/v1/projects'],
'members' => ['path' => '/v1/members'],
'services' => ['path' => '/v1/services'],
];
}
@@ -75,6 +81,9 @@ class AdminApiContractTest extends TestCase
$page = Page::factory()->create();
$category = Category::factory()->create();
$technology = Technology::factory()->create();
$project = Project::factory()->create();
$member = Member::factory()->create();
$service = Service::factory()->create();
// One request, one documented path — the URL a real caller hits and
// the path key that describes it, side by side rather than derived
@@ -87,6 +96,12 @@ class AdminApiContractTest extends TestCase
'category' => ["/v1/categories/{$category->id}", '/v1/categories/{category}'],
'technologies' => ['/v1/technologies', '/v1/technologies'],
'technology' => ["/v1/technologies/{$technology->id}", '/v1/technologies/{technology}'],
'projects' => ['/v1/projects', '/v1/projects'],
'project' => ["/v1/projects/{$project->id}", '/v1/projects/{project}'],
'members' => ['/v1/members', '/v1/members'],
'member' => ["/v1/members/{$member->id}", '/v1/members/{member}'],
'services' => ['/v1/services', '/v1/services'],
'service' => ["/v1/services/{$service->id}", '/v1/services/{service}'],
'users' => ['/v1/users', '/v1/users'],
'user' => ["/v1/users/{$this->caller->id}", '/v1/users/{user}'],
'me' => ['/v1/me', '/v1/me'],