Compare commits
5
Commits
393d074bb7
..
v0.3.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
704fe5d241 | ||
|
|
73f6ab6918 | ||
|
|
6d4a823fe5 | ||
|
|
6054690607 | ||
|
|
723285f820 |
@@ -3,6 +3,7 @@
|
||||
namespace App\Actions\Projects;
|
||||
|
||||
use App\Models\Project;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DeleteProject
|
||||
{
|
||||
@@ -10,7 +11,12 @@ class DeleteProject
|
||||
{
|
||||
$projectName = $project->name;
|
||||
|
||||
$project->delete();
|
||||
DB::transaction(function () use ($project): void {
|
||||
$baseTemplate = $project->baseTemplate()->first();
|
||||
|
||||
$project->delete();
|
||||
$baseTemplate?->delete();
|
||||
});
|
||||
|
||||
return $projectName;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class DeleteProjectRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('access-admin') ?? false;
|
||||
return $this->user()?->can('admin-project', $this->route('project')) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Generated
+5
-5
@@ -2007,16 +2007,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "2.10.0",
|
||||
"version": "2.10.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4"
|
||||
"reference": "6efbd9c472b91db0a3350fcd601c8332c2382e1f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d2d1aa8b35e072966c89bc0c66cf926e56767dc4",
|
||||
"reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6efbd9c472b91db0a3350fcd601c8332c2382e1f",
|
||||
"reference": "6efbd9c472b91db0a3350fcd601c8332c2382e1f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2110,7 +2110,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-08-11T16:06:25+00:00"
|
||||
"time": "2026-09-21T13:07:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/config",
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
"dev": "vite",
|
||||
"lint": "eslint . --fix",
|
||||
"lint:check": "eslint .",
|
||||
"types:check": "vue-tsc --noEmit"
|
||||
"types:check": "php artisan wayfinder:generate --with-form --no-interaction && vue-tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
|
||||
Generated
+13
-5
@@ -203,6 +203,14 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
axios@>=1.13.0 <1.20.0: ^1.20.0
|
||||
axios@>=1.15.0 <1.20.0: ^1.20.0
|
||||
axios@>=1.15.1 <1.20.0: ^1.20.0
|
||||
axios@>=1.15.2 <1.20.0: ^1.20.0
|
||||
axios@>=1.16.1 <1.20.0: ^1.20.0
|
||||
axios@>=1.17.0 <1.20.0: ^1.20.0
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -1075,8 +1083,8 @@ packages:
|
||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
axios@1.19.0:
|
||||
resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==}
|
||||
axios@1.20.0:
|
||||
resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
@@ -2671,7 +2679,7 @@ snapshots:
|
||||
'@inertiajs/core@2.3.18(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)':
|
||||
dependencies:
|
||||
'@types/lodash-es': 4.17.12
|
||||
axios: 1.19.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
axios: 1.20.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
laravel-precognition: 1.0.2(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
lodash-es: 4.18.1
|
||||
qs: 6.16.0
|
||||
@@ -3287,7 +3295,7 @@ snapshots:
|
||||
dependencies:
|
||||
possible-typed-array-names: 1.1.0
|
||||
|
||||
axios@1.19.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1):
|
||||
axios@1.20.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1):
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0(debug@4.4.3(supports-color@8.1.1))
|
||||
form-data: 4.0.6
|
||||
@@ -4055,7 +4063,7 @@ snapshots:
|
||||
|
||||
laravel-precognition@1.0.2(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1):
|
||||
dependencies:
|
||||
axios: 1.19.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
axios: 1.20.0(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
lodash-es: 4.18.1
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
@@ -7,3 +7,10 @@ minimumReleaseAgeExclude:
|
||||
allowBuilds:
|
||||
unrs-resolver: true
|
||||
vue-demi: true
|
||||
overrides:
|
||||
axios@>=1.13.0 <1.20.0: ^1.20.0
|
||||
axios@>=1.15.0 <1.20.0: ^1.20.0
|
||||
axios@>=1.15.1 <1.20.0: ^1.20.0
|
||||
axios@>=1.15.2 <1.20.0: ^1.20.0
|
||||
axios@>=1.16.1 <1.20.0: ^1.20.0
|
||||
axios@>=1.17.0 <1.20.0: ^1.20.0
|
||||
|
||||
@@ -27,7 +27,7 @@ Route::prefix('projects')->name('projects.')->group(function (): void {
|
||||
});
|
||||
|
||||
Route::delete('/', [ProjectController::class, 'destroy'])
|
||||
->middleware('can:access-admin')
|
||||
->middleware('can:admin-project,project')
|
||||
->name('destroy');
|
||||
|
||||
Route::middleware('can:admin-project,project')->group(function (): void {
|
||||
|
||||
@@ -291,7 +291,14 @@ class ProjectApiTest extends TestCase
|
||||
->assertOk()
|
||||
->assertJsonPath('data.key', 'CORE');
|
||||
|
||||
$this->syncGlobalPermissions($admin, ['admin']);
|
||||
$this->withApplicationKey($user)
|
||||
->deleteJson(route('api.projects.destroy', $project), [
|
||||
'confirmation_key' => 'CORE',
|
||||
])
|
||||
->assertForbidden();
|
||||
|
||||
$this->syncProjectPermissions($project, $admin, ['admin']);
|
||||
$this->assertTrue($admin->can('admin-project', $project));
|
||||
|
||||
$this->withApplicationKey($admin)
|
||||
->deleteJson(route('api.projects.destroy', $project), [
|
||||
|
||||
@@ -817,6 +817,75 @@ class ProjectControllerTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_project_admins_can_delete_their_own_projects_and_reuse_the_key(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$this->syncGlobalPermissions($user, ['create']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('projects.store'), [
|
||||
'creation_mode' => 'scratch',
|
||||
'name' => 'Core Platform',
|
||||
'key' => 'CORE',
|
||||
])
|
||||
->assertRedirect();
|
||||
|
||||
$project = Project::query()->where('key', 'CORE')->firstOrFail();
|
||||
$baseTemplateId = $project->project_template_id;
|
||||
$this->assertTrue($user->can('admin-project', $project));
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.destroy', $project), [
|
||||
'confirmation_key' => 'CORE',
|
||||
])
|
||||
->assertRedirect(route('projects.index'));
|
||||
|
||||
$this->assertDatabaseMissing('projects', ['id' => $project->id]);
|
||||
$this->assertDatabaseMissing('project_templates', ['id' => $baseTemplateId]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('projects.store'), [
|
||||
'creation_mode' => 'scratch',
|
||||
'name' => 'New Core Platform',
|
||||
'key' => 'CORE',
|
||||
])
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertDatabaseHas('projects', [
|
||||
'key' => 'CORE',
|
||||
'name' => 'New Core Platform',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_deleting_a_project_preserves_its_published_template_and_linked_projects(): void
|
||||
{
|
||||
$admin = User::factory()->admin()->create();
|
||||
$project = Project::factory()->create(['key' => 'CORE']);
|
||||
$otherProject = Project::factory()->create(['key' => 'OTHER']);
|
||||
$template = ProjectTemplate::factory()->create([
|
||||
'source_project_id' => $project->id,
|
||||
'is_published' => true,
|
||||
]);
|
||||
|
||||
$project->update(['project_template_id' => $template->id]);
|
||||
$otherProject->update(['project_template_id' => $template->id]);
|
||||
|
||||
$this->actingAs($admin)
|
||||
->delete(route('projects.destroy', $project), [
|
||||
'confirmation_key' => 'CORE',
|
||||
])
|
||||
->assertRedirect(route('projects.index'));
|
||||
|
||||
$this->assertDatabaseHas('project_templates', [
|
||||
'id' => $template->id,
|
||||
'source_project_id' => null,
|
||||
]);
|
||||
$this->assertDatabaseHas('projects', [
|
||||
'id' => $otherProject->id,
|
||||
'project_template_id' => $template->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure project deletion requires the exact project key confirmation.
|
||||
*/
|
||||
@@ -841,16 +910,18 @@ class ProjectControllerTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure non-admin users cannot delete projects.
|
||||
* Ensure users without project administration cannot delete projects.
|
||||
*/
|
||||
public function test_non_admin_users_cannot_delete_projects(): void
|
||||
public function test_project_readers_cannot_delete_projects(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$otherProject = Project::factory()->create();
|
||||
$project = Project::factory()->create([
|
||||
'key' => 'CORE',
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['admin']);
|
||||
$this->syncProjectPermissions($otherProject, $user, ['admin']);
|
||||
$this->syncProjectPermissions($project, $user, ['view']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.destroy', $project), [
|
||||
|
||||
Reference in New Issue
Block a user