Add template switch and reset flows for existing projects

This commit is contained in:
2026-05-25 08:31:35 +00:00
parent ee9b9a2765
commit e38c77198d
10 changed files with 522 additions and 2 deletions
@@ -0,0 +1,44 @@
<?php
namespace App\Actions\Projects;
use App\Models\Project;
use App\Models\ProjectTemplate;
use Illuminate\Support\Facades\DB;
class UpdateProjectTemplateLink
{
public function __construct(
private readonly DuplicateProjectConfiguration $duplicateProjectConfiguration,
) {}
public function switch(Project $project, ProjectTemplate $template): void
{
DB::transaction(function () use ($project, $template): void {
$project->forceFill([
'project_template_id' => $template->id,
])->save();
$this->duplicateProjectConfiguration->apply(
$project->fresh(),
$template->configuration_snapshot ?? [],
);
});
}
public function reset(Project $project): void
{
$template = $project->projectTemplate;
if ($template === null) {
return;
}
DB::transaction(function () use ($project, $template): void {
$this->duplicateProjectConfiguration->apply(
$project->fresh(),
$template->configuration_snapshot ?? [],
);
});
}
}
+39 -1
View File
@@ -9,17 +9,20 @@ use App\Actions\Projects\DeleteProject;
use App\Actions\Projects\DuplicateProjectConfiguration;
use App\Actions\Projects\ListVisibleProjects;
use App\Actions\Projects\ResolveProjectTemplateState;
use App\Actions\Projects\UpdateProjectTemplateLink;
use App\Enums\Permission;
use App\Http\Requests\DeleteProjectRequest;
use App\Http\Requests\ResetProjectTemplateRequest;
use App\Http\Requests\StoreProjectRequest;
use App\Http\Requests\SwitchProjectTemplateRequest;
use App\Http\Requests\SyncProjectPermissionsRequest;
use App\Http\Resources\PermissionOptionResource;
use App\Http\Resources\ProjectResource;
use App\Http\Resources\ProjectTemplateResource;
use App\Models\ProjectTemplate;
use App\Http\Resources\TeamResource;
use App\Http\Resources\UserResource;
use App\Models\Project;
use App\Models\ProjectTemplate;
use App\Models\Team;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
@@ -37,6 +40,7 @@ class ProjectController extends Controller
private readonly ListVisibleProjects $listVisibleProjects,
private readonly PermissionService $permissionService,
private readonly ResolveProjectTemplateState $resolveProjectTemplateState,
private readonly UpdateProjectTemplateLink $updateProjectTemplateLink,
) {}
public function index(Request $request): Response
@@ -117,10 +121,44 @@ class ProjectController extends Controller
{
return Inertia::render('projects/settings/General', [
'project' => ProjectResource::make($project)->resolve(),
'sourceTemplates' => ProjectTemplateResource::collection(
ProjectTemplate::query()
->published()
->with('sourceProject')
->orderBy('name')
->get(),
)->resolve(),
'status' => session('status'),
'templateState' => $this->resolveProjectTemplateState->handle($project),
]);
}
public function switchTemplate(SwitchProjectTemplateRequest $request, Project $project): RedirectResponse
{
$template = ProjectTemplate::query()
->published()
->findOrFail($request->integer('source_template_id'));
$this->updateProjectTemplateLink->switch($project, $template);
return to_route('projects.settings.general', $project)
->with('status', "{$project->name} now uses the {$template->name} template.");
}
public function resetTemplate(ResetProjectTemplateRequest $request, Project $project): RedirectResponse
{
$request->validated();
$template = $project->projectTemplate;
abort_if($template === null, 422, 'This project has no linked template to reset.');
$this->updateProjectTemplateLink->reset($project->load('projectTemplate'));
return to_route('projects.settings.general', $project)
->with('status', "{$project->name} was reset to the {$template->name} template baseline.");
}
public function access(Request $request, Project $project): Response
{
$user = $request->user();
@@ -0,0 +1,43 @@
<?php
namespace App\Http\Requests;
use App\Models\Project;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ResetProjectTemplateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
/** @var Project $project */
$project = $this->route('project');
return [
'confirmation_key' => [
'required',
'string',
Rule::in([$project->key]),
],
];
}
/**
* @return array<string, string>
*/
public function messages(): array
{
return [
'confirmation_key.in' => 'Enter the exact project key to reset this project to its template baseline.',
];
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Http\Requests;
use App\Models\Project;
use App\Models\ProjectTemplate;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class SwitchProjectTemplateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
/** @var Project $project */
$project = $this->route('project');
return [
'confirmation_key' => [
'required',
'string',
Rule::in([$project->key]),
],
'source_template_id' => [
'required',
'integer',
Rule::exists(ProjectTemplate::class, 'id')->where('is_published', true),
Rule::notIn(array_filter([$project->project_template_id])),
],
];
}
/**
* @return array<string, string>
*/
public function messages(): array
{
return [
'confirmation_key.in' => 'Enter the exact project key to confirm the template switch.',
'source_template_id.not_in' => 'Choose a different published template to switch this project.',
];
}
}