Add reusable project templates
This commit is contained in:
@@ -14,6 +14,8 @@ use App\Http\Requests\StoreProjectRequest;
|
||||
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;
|
||||
@@ -52,11 +54,18 @@ class ProjectController extends Controller
|
||||
403,
|
||||
);
|
||||
|
||||
$sourceTemplates = ProjectTemplate::query()->with('sourceProject')->orderBy('name')->get();
|
||||
$selectedTemplateId = $request->integer('template');
|
||||
|
||||
return Inertia::render('projects/Create', [
|
||||
'copyAreaOptions' => DuplicateProjectConfiguration::copyAreaOptions(),
|
||||
'sourceProjects' => ProjectResource::collection(
|
||||
$this->listVisibleProjects->handle($request->user()),
|
||||
)->resolve(),
|
||||
'sourceTemplates' => ProjectTemplateResource::collection($sourceTemplates)->resolve(),
|
||||
'selectedSourceTemplateId' => $sourceTemplates->contains('id', $selectedTemplateId)
|
||||
? $selectedTemplateId
|
||||
: null,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Projects\CreateProjectTemplate;
|
||||
use App\Actions\Projects\DuplicateProjectConfiguration;
|
||||
use App\Http\Requests\StoreProjectTemplateRequest;
|
||||
use App\Http\Resources\ProjectResource;
|
||||
use App\Http\Resources\ProjectTemplateResource;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectTemplate;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProjectTemplateController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CreateProjectTemplate $createProjectTemplate,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
return Inertia::render('project-templates/Index', [
|
||||
'status' => session('status'),
|
||||
'templates' => ProjectTemplateResource::collection(
|
||||
ProjectTemplate::query()
|
||||
->with('sourceProject')
|
||||
->orderBy('name')
|
||||
->get(),
|
||||
)->resolve(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(Project $project): Response
|
||||
{
|
||||
return Inertia::render('project-templates/Create', [
|
||||
'copyAreaOptions' => DuplicateProjectConfiguration::copyAreaOptions(),
|
||||
'project' => ProjectResource::make($project)->resolve(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreProjectTemplateRequest $request, Project $project): RedirectResponse
|
||||
{
|
||||
$template = $this->createProjectTemplate->handle($project, $request->validated());
|
||||
|
||||
return to_route('project-templates.index')
|
||||
->with('status', "Template {$template->name} was created.");
|
||||
}
|
||||
|
||||
public function destroy(ProjectTemplate $projectTemplate): RedirectResponse
|
||||
{
|
||||
$templateName = $projectTemplate->name;
|
||||
$projectTemplate->delete();
|
||||
|
||||
return to_route('project-templates.index')
|
||||
->with('status', "Template {$templateName} was deleted.");
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class StoreProjectRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'creation_mode' => ['required', 'string', Rule::in(['scratch', 'duplicate'])],
|
||||
'creation_mode' => ['required', 'string', Rule::in(['scratch', 'duplicate', 'template'])],
|
||||
'key' => [
|
||||
'required',
|
||||
'string',
|
||||
@@ -81,6 +81,12 @@ class StoreProjectRequest extends FormRequest
|
||||
}
|
||||
},
|
||||
],
|
||||
'source_template_id' => [
|
||||
Rule::requiredIf(fn (): bool => $this->input('creation_mode') === 'template'),
|
||||
'nullable',
|
||||
'integer',
|
||||
Rule::exists('project_templates', 'id'),
|
||||
],
|
||||
'copy_areas' => ['nullable', 'array'],
|
||||
'copy_areas.*' => [
|
||||
'string',
|
||||
@@ -96,6 +102,7 @@ class StoreProjectRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'source_project_id.required' => 'Select a source project to duplicate from.',
|
||||
'source_template_id.required' => 'Select a template to create the project from.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Actions\Projects\DuplicateProjectConfiguration;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreProjectTemplateRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()?->can('create-project') ?? false;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->filled('slug')) {
|
||||
$this->merge([
|
||||
'slug' => Str::slug((string) $this->input('slug')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'slug' => ['required', 'string', 'max:255', 'alpha_dash', Rule::unique('project_templates', 'slug')],
|
||||
'copy_areas' => ['required', 'array', 'min:1'],
|
||||
'copy_areas.*' => ['string', Rule::in(DuplicateProjectConfiguration::copyableAreas())],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\ProjectTemplate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class ProjectTemplateResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
if ($this->resource instanceof ProjectTemplate) {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'source_project_id' => $this->source_project_id,
|
||||
'source_project' => $this->relationLoaded('sourceProject') && $this->sourceProject !== null
|
||||
? ProjectResource::make($this->sourceProject)->resolve()
|
||||
: null,
|
||||
'name' => $this->name,
|
||||
'slug' => $this->slug,
|
||||
'copy_areas' => $this->copy_areas ?? [],
|
||||
'created_at' => $this->created_at?->toISOString(),
|
||||
'updated_at' => $this->updated_at?->toISOString(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => Arr::get($this->resource, 'id'),
|
||||
'source_project_id' => Arr::get($this->resource, 'source_project_id'),
|
||||
'source_project' => Arr::get($this->resource, 'source_project'),
|
||||
'name' => Arr::get($this->resource, 'name'),
|
||||
'slug' => Arr::get($this->resource, 'slug'),
|
||||
'copy_areas' => Arr::get($this->resource, 'copy_areas', []),
|
||||
'created_at' => Arr::get($this->resource, 'created_at'),
|
||||
'updated_at' => Arr::get($this->resource, 'updated_at'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user