feat(ideas): add private draft crud#43
Conversation
There was a problem hiding this comment.
Pull request overview
Adds authenticated, owner-scoped CRUD for private “Idea” drafts in the Ideas app, enforcing ownership via a policy and providing UI for creating/editing/deleting drafts.
Changes:
- Introduces
IdeaControllerwith resource routes for draft CRUD (excludingshow) behindauth. - Adds
IdeaPolicyand registers it to enforce per-user ownership for update/delete. - Adds create/edit views and shared form partial; updates index UI with “New idea” and “Edit” affordances; expands UI components (
button,textarea) to support these flows. - Adds Pest feature coverage for create/edit/update/delete and unauthorized access.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Feature/Ideas/IdeasDraftCrudTest.php | Feature tests covering owner CRUD and forbidden access for non-owners. |
| routes/web.php | Replaces ideas index closure with resource routes to IdeaController. |
| app/Http/Controllers/IdeaController.php | Implements index/create/store/edit/update/destroy with Gate authorization. |
| app/Policies/IdeaPolicy.php | Adds ownership-based authorization rules for Idea drafts. |
| app/Providers/AppServiceProvider.php | Registers the IdeaPolicy for the Idea model. |
| resources/views/ideas/index.blade.php | Adds “New idea” action and per-idea “Edit” link. |
| resources/views/ideas/create.blade.php | New draft creation page using shared form partial. |
| resources/views/ideas/edit.blade.php | Draft edit + delete UI using shared form partial. |
| resources/views/ideas/_form.blade.php | Shared create/edit form partial. |
| resources/views/components/ui/textarea.blade.php | Adds value prop support for textarea content. |
| resources/views/components/ui/button.blade.php | Adds as prop to render as anchor or button. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $validated = $request->validate([ | ||
| 'title' => ['required', 'string', 'max:255'], | ||
| 'summary' => ['required', 'string', 'max:500'], | ||
| 'details' => ['required', 'string'], | ||
| ]); |
There was a problem hiding this comment.
The draft workflow/issue text mentions saving partial work, and the ideas table allows summary/details to be NULL, but the store validation currently requires both fields. This prevents creating a minimal draft (e.g., title-only) and forces users to fill everything upfront. Consider making summary/details nullable/optional in validation (and handling empty strings consistently) so partial drafts can be saved.
| $validated = $request->validate([ | ||
| 'title' => ['required', 'string', 'max:255'], | ||
| 'summary' => ['required', 'string', 'max:500'], | ||
| 'details' => ['required', 'string'], |
There was a problem hiding this comment.
Update validation requires summary and details, which means users cannot progressively fill in a draft or clear a field once saved. If drafts are intended to support partial/incremental writing, make these fields optional on update as well (and decide whether an empty string should be stored as NULL).
| $validated = $request->validate([ | |
| 'title' => ['required', 'string', 'max:255'], | |
| 'summary' => ['required', 'string', 'max:500'], | |
| 'details' => ['required', 'string'], | |
| $request->merge([ | |
| 'summary' => $request->input('summary') === '' ? null : $request->input('summary'), | |
| 'details' => $request->input('details') === '' ? null : $request->input('details'), | |
| ]); | |
| $validated = $request->validate([ | |
| 'title' => ['required', 'string', 'max:255'], | |
| 'summary' => ['nullable', 'string', 'max:500'], | |
| 'details' => ['nullable', 'string'], |
| <x-ui.input label="Summary" name="summary" :value="old('summary', $idea?->summary)" required /> | ||
| <x-ui.textarea label="Details" name="details" rows="10" required :value="old('details', $idea?->details)" /> |
There was a problem hiding this comment.
The form marks Summary and Details as required, which locks the UI into the same limitation as the controller validation. If drafts should allow partial saves (e.g., title-only), consider removing required here and letting validation rules drive what’s mandatory.
| <x-ui.input label="Summary" name="summary" :value="old('summary', $idea?->summary)" required /> | |
| <x-ui.textarea label="Details" name="details" rows="10" required :value="old('details', $idea?->details)" /> | |
| <x-ui.input label="Summary" name="summary" :value="old('summary', $idea?->summary)" /> | |
| <x-ui.textarea label="Details" name="details" rows="10" :value="old('details', $idea?->details)" /> |
| public function view(User $user, Idea $idea): bool | ||
| { | ||
| return $idea->user->is($user); | ||
| } |
There was a problem hiding this comment.
These policy checks use $idea->user->is($user), which will lazy-load the user relationship during authorization (extra query per request) when it isn’t already loaded. Since ownership is stored on the model, comparing $idea->user_id === $user->id avoids the query and keeps auth checks cheap.
Closes #22\n\nAdds private draft CRUD for the Ideas app:\n- authenticated users can create, edit, and delete their own drafts\n- ideas are owned by the current user and stay private by default\n- ownership is enforced by policy\n- includes Pest coverage for create/edit/update/delete and unauthorized access