docs(01): create phase plan — 2 plans for database schema and backend API

This commit is contained in:
2026-02-13 11:56:25 +08:00
parent 3715aae2eb
commit 2257cdc03f
3 changed files with 427 additions and 3 deletions

View File

@@ -0,0 +1,181 @@
---
phase: 01-database-schema-backend-api
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- database/migrations/YYYY_MM_DD_HHMMSS_create_notes_table.php
- app/Models/Note.php
- app/Models/Member.php
- app/Providers/AppServiceProvider.php
- database/factories/NoteFactory.php
autonomous: true
must_haves:
truths:
- "Notes table exists with polymorphic columns (notable_type, notable_id) and composite index"
- "Note model has morphTo relationship to notable and belongsTo relationship to author (User)"
- "Member model has morphMany relationship to notes ordered by created_at desc"
- "Morph map registered in AppServiceProvider maps 'member' to Member::class"
- "NoteFactory can generate test notes with forMember() state method"
artifacts:
- path: "database/migrations/*_create_notes_table.php"
provides: "Notes table schema with polymorphic columns and indexes"
contains: "morphs('notable')"
- path: "app/Models/Note.php"
provides: "Note model with relationships"
exports: ["Note"]
min_lines: 25
- path: "app/Models/Member.php"
provides: "notes() morphMany relationship added to existing model"
contains: "morphMany(Note::class"
- path: "app/Providers/AppServiceProvider.php"
provides: "Morph map registration for namespace safety"
contains: "enforceMorphMap"
- path: "database/factories/NoteFactory.php"
provides: "Factory for test note creation"
contains: "forMember"
key_links:
- from: "app/Models/Note.php"
to: "app/Models/Member.php"
via: "morphTo/morphMany polymorphic relationship"
pattern: "morphTo|morphMany"
- from: "app/Models/Note.php"
to: "app/Models/User.php"
via: "belongsTo author relationship"
pattern: "belongsTo.*User::class.*author_user_id"
- from: "app/Providers/AppServiceProvider.php"
to: "app/Models/Member.php"
via: "Morph map registration"
pattern: "enforceMorphMap.*member.*Member::class"
---
<objective>
Create the database foundation for the member notes system: migration, Note model with polymorphic relationships, Member model relationship addition, morph map registration, and test factory.
Purpose: Establishes the data layer that all subsequent note features depend on. Without this, no notes can be stored or queried.
Output: Notes table in database, Note model, Member->notes relationship, NoteFactory for testing.
</objective>
<execution_context>
@/Users/gbanyan/.claude/get-shit-done/workflows/execute-plan.md
@/Users/gbanyan/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-database-schema-backend-api/01-RESEARCH.md
@app/Models/Member.php
@app/Models/CustomFieldValue.php
@app/Providers/AppServiceProvider.php
@database/factories/MemberFactory.php
</context>
<tasks>
<task type="auto">
<name>Task 1: Create notes migration and Note model with polymorphic relationships</name>
<files>
database/migrations/YYYY_MM_DD_HHMMSS_create_notes_table.php
app/Models/Note.php
</files>
<action>
Create migration using `php artisan make:migration create_notes_table`. In the migration:
- `$table->id()`
- `$table->morphs('notable')` — creates notable_type (string), notable_id (unsignedBigInteger), and composite index on [notable_type, notable_id] automatically
- `$table->longText('content')` — note text content
- `$table->foreignId('author_user_id')->constrained('users')->cascadeOnDelete()` — links to User who wrote the note
- `$table->timestamps()`
- `$table->index('created_at')` — for chronological sorting queries
Create `app/Models/Note.php`:
- Use `HasFactory` trait
- `$fillable`: notable_type, notable_id, content, author_user_id
- `notable()` method returning `$this->morphTo()` (MorphTo relationship)
- `author()` method returning `$this->belongsTo(User::class, 'author_user_id')` (BelongsTo relationship)
- Follow existing model patterns from CustomFieldValue.php (same polymorphic pattern)
Run `php artisan migrate` to apply the migration.
</action>
<verify>
Run `php artisan migrate:status` and confirm the create_notes_table migration shows as "Ran".
Run `php artisan tinker --execute="Schema::hasTable('notes')"` and confirm it returns true.
Run `php artisan tinker --execute="Schema::getColumnListing('notes')"` and confirm columns: id, notable_type, notable_id, content, author_user_id, created_at, updated_at.
</verify>
<done>
Notes table exists in database with all columns (id, notable_type, notable_id, content, author_user_id, created_at, updated_at), composite index on [notable_type, notable_id], and index on created_at. Note model exists with notable() morphTo and author() belongsTo relationships.
</done>
</task>
<task type="auto">
<name>Task 2: Add Member relationship, morph map, and test factory</name>
<files>
app/Models/Member.php
app/Providers/AppServiceProvider.php
database/factories/NoteFactory.php
</files>
<action>
In `app/Models/Member.php`:
- Add `use Illuminate\Database\Eloquent\Relations\MorphMany;` import
- Add `notes()` method returning `$this->morphMany(Note::class, 'notable')->orderBy('created_at', 'desc')` — default ordering newest first for display
- Add `use App\Models\Note;` import
- Place the method near existing relationship methods (after payments, user, etc.)
In `app/Providers/AppServiceProvider.php`:
- Add `use Illuminate\Database\Eloquent\Relations\Relation;` import
- Add `use App\Models\Member;` import
- In `boot()` method, add: `Relation::enforceMorphMap(['member' => Member::class]);`
- This ensures 'member' is stored in notable_type column instead of the full class name 'App\Models\Member', protecting against future namespace refactoring
Create `database/factories/NoteFactory.php`:
- Follow existing MemberFactory pattern
- `definition()` returns: notable_type => 'member' (uses morph map alias, NOT Member::class), notable_id => Member::factory(), content => $this->faker->paragraph(), author_user_id => User::factory()
- Add `forMember(Member $member)` state method that sets notable_type => 'member', notable_id => $member->id
- Add `byAuthor(User $user)` state method that sets author_user_id => $user->id
</action>
<verify>
Run `php artisan tinker --execute="use App\Models\Member; use App\Models\Note; echo (new Member)->notes() instanceof \Illuminate\Database\Eloquent\Relations\MorphMany ? 'OK' : 'FAIL';"` and confirm "OK".
Run `php artisan tinker --execute="use Illuminate\Database\Eloquent\Relations\Relation; echo json_encode(Relation::morphMap());"` and confirm output contains "member" key mapping to Member class.
Run `php artisan tinker --execute="use App\Models\Note; echo class_exists(\Database\Factories\NoteFactory::class) ? 'OK' : 'FAIL';"` and confirm "OK".
</verify>
<done>
Member model has notes() morphMany relationship returning notes ordered by created_at desc. AppServiceProvider registers morph map with 'member' => Member::class. NoteFactory exists with definition(), forMember(), and byAuthor() state methods.
</done>
</task>
</tasks>
<verification>
Run `php artisan migrate:fresh --seed` to confirm migration works cleanly with existing seeders.
Run `php artisan tinker` and execute:
```php
use App\Models\Member;
use App\Models\User;
use App\Models\Note;
$user = User::first();
$member = Member::first();
$note = $member->notes()->create(['content' => 'Test note', 'author_user_id' => $user->id]);
echo $note->id . ' - ' . $note->notable_type . ' - ' . $note->content;
echo $note->author->name;
echo $member->notes()->count();
```
All commands should execute without errors. The notable_type should show 'member' (not 'App\Models\Member') due to the morph map.
</verification>
<success_criteria>
1. Notes table exists with all required columns and indexes
2. Note model has working morphTo and belongsTo relationships
3. Member model has working morphMany notes relationship (ordered by created_at desc)
4. Morph map stores 'member' string (not full class name) in notable_type
5. NoteFactory can create notes with forMember() and byAuthor() state methods
6. `php artisan migrate:fresh --seed` runs without errors
</success_criteria>
<output>
After completion, create `.planning/phases/01-database-schema-backend-api/01-01-SUMMARY.md`
</output>