---
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"
---
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.
@/Users/gbanyan/.claude/get-shit-done/workflows/execute-plan.md
@/Users/gbanyan/.claude/get-shit-done/templates/summary.md
@.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
Task 1: Create notes migration and Note model with polymorphic relationships
database/migrations/YYYY_MM_DD_HHMMSS_create_notes_table.php
app/Models/Note.php
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.
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.
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.
Task 2: Add Member relationship, morph map, and test factory
app/Models/Member.php
app/Providers/AppServiceProvider.php
database/factories/NoteFactory.php
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
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".
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.
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.
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