feat: add category management
associate with entries and text widgets
This commit is contained in:
parent
c83028b4d4
commit
9b9e1a8e29
22 changed files with 392 additions and 46 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
added tags to entry model
|
||||
|
||||
added text widget and category
|
||||
|
||||
## 2026-01-07
|
||||
|
||||
added simple API for entries model
|
||||
|
|
|
|||
50
app/Filament/Resources/Categroys/CategroyResource.php
Normal file
50
app/Filament/Resources/Categroys/CategroyResource.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categroys;
|
||||
|
||||
use App\Filament\Resources\Categroys\Pages\CreateCategroy;
|
||||
use App\Filament\Resources\Categroys\Pages\EditCategroy;
|
||||
use App\Filament\Resources\Categroys\Pages\ListCategroys;
|
||||
use App\Filament\Resources\Categroys\Schemas\CategroyForm;
|
||||
use App\Filament\Resources\Categroys\Tables\CategroysTable;
|
||||
use App\Models\Category;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CategroyResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Category::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::RectangleGroup;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return CategroyForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return CategroysTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListCategroys::route('/'),
|
||||
'create' => CreateCategroy::route('/create'),
|
||||
'edit' => EditCategroy::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Categroys/Pages/CreateCategroy.php
Normal file
11
app/Filament/Resources/Categroys/Pages/CreateCategroy.php
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categroys\Pages;
|
||||
|
||||
use App\Filament\Resources\Categroys\CategroyResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCategroy extends CreateRecord
|
||||
{
|
||||
protected static string $resource = CategroyResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Categroys/Pages/EditCategroy.php
Normal file
19
app/Filament/Resources/Categroys/Pages/EditCategroy.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categroys\Pages;
|
||||
|
||||
use App\Filament\Resources\Categroys\CategroyResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditCategroy extends EditRecord
|
||||
{
|
||||
protected static string $resource = CategroyResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Categroys/Pages/ListCategroys.php
Normal file
19
app/Filament/Resources/Categroys/Pages/ListCategroys.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categroys\Pages;
|
||||
|
||||
use App\Filament\Resources\Categroys\CategroyResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListCategroys extends ListRecords
|
||||
{
|
||||
protected static string $resource = CategroyResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/Filament/Resources/Categroys/Schemas/CategroyForm.php
Normal file
20
app/Filament/Resources/Categroys/Schemas/CategroyForm.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categroys\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class CategroyForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label('Category Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
}
|
||||
34
app/Filament/Resources/Categroys/Tables/CategroysTable.php
Normal file
34
app/Filament/Resources/Categroys/Tables/CategroysTable.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categroys\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CategroysTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label('Category Name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,19 +2,19 @@
|
|||
|
||||
namespace App\Filament\Resources\Entries\Schemas;
|
||||
|
||||
use App\Models\Category;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\SpatieMediaLibraryFileUpload;
|
||||
use Filament\Forms\Components\SpatieTagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
use Filament\Forms\Components\SpatieTagsInput;
|
||||
|
||||
class EntryForm
|
||||
{
|
||||
|
|
@ -67,12 +67,12 @@ class EntryForm
|
|||
$fileName = e($item->file_name);
|
||||
$name = e($item->name ?? '');
|
||||
|
||||
$html = "<div class='flex items-center gap-3'>" .
|
||||
"<img src='{$url}' class='rounded' style='width:60px;height:60px;object-fit:cover;' alt='{$fileName}' loading='lazy' />" .
|
||||
"<div class='flex flex-col'>" .
|
||||
"<span class='font-medium text-sm'>{$name}</span>" .
|
||||
"<span class='text-xs text-gray-500'>{$fileName}</span>" .
|
||||
"</div></div>";
|
||||
$html = "<div class='flex items-center gap-3'>".
|
||||
"<img src='{$url}' class='rounded' style='width:60px;height:60px;object-fit:cover;' alt='{$fileName}' loading='lazy' />".
|
||||
"<div class='flex flex-col'>".
|
||||
"<span class='font-medium text-sm'>{$name}</span>".
|
||||
"<span class='text-xs text-gray-500'>{$fileName}</span>".
|
||||
'</div></div>';
|
||||
|
||||
return [$item->id => $html];
|
||||
} catch (\Exception $e) {
|
||||
|
|
@ -87,38 +87,41 @@ class EntryForm
|
|||
->action(function (array $data, SpatieMediaLibraryFileUpload $component): void {
|
||||
$record = $component->getRecord();
|
||||
|
||||
if (!$record) {
|
||||
if (! $record) {
|
||||
\Filament\Notifications\Notification::make()
|
||||
->warning()
|
||||
->title('Save the entry first')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$data['image_id']) {
|
||||
if (! $data['image_id']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sourceMedia = Media::find($data['image_id']);
|
||||
if (!$sourceMedia || !file_exists($sourceMedia->getPath())) {
|
||||
if (! $sourceMedia || ! file_exists($sourceMedia->getPath())) {
|
||||
\Filament\Notifications\Notification::make()
|
||||
->danger()
|
||||
->title('Image file not found')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sourceFile = $sourceMedia->getPath();
|
||||
$tempCopy = sys_get_temp_dir() . '/' . uniqid() . '_' . $sourceMedia->file_name;
|
||||
$tempCopy = sys_get_temp_dir().'/'.uniqid().'_'.$sourceMedia->file_name;
|
||||
copy($sourceFile, $tempCopy);
|
||||
|
||||
try {
|
||||
// Verify record has ID
|
||||
if (!$record->id) {
|
||||
if (! $record->id) {
|
||||
\Filament\Notifications\Notification::make()
|
||||
->danger()
|
||||
->title('Entry must be saved first')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +141,7 @@ class EntryForm
|
|||
} catch (\Exception $e) {
|
||||
\Filament\Notifications\Notification::make()
|
||||
->danger()
|
||||
->title('Error: ' . $e->getMessage())
|
||||
->title('Error: '.$e->getMessage())
|
||||
->send();
|
||||
} finally {
|
||||
if (file_exists($tempCopy)) {
|
||||
|
|
@ -152,6 +155,14 @@ class EntryForm
|
|||
Toggle::make('is_featured')
|
||||
->required(),
|
||||
DatePicker::make('published_at'),
|
||||
Select::make('category_id')
|
||||
->label('Category')
|
||||
->options(function () {
|
||||
return Category::all()
|
||||
->pluck('name', 'id')
|
||||
->toArray();
|
||||
})
|
||||
->searchable(),
|
||||
RichEditor::make('content')
|
||||
->columnSpanFull()
|
||||
->hintAction(
|
||||
|
|
@ -182,12 +193,12 @@ class EntryForm
|
|||
$name = e($item->name ?? '');
|
||||
|
||||
// Smaller image preview for better performance
|
||||
$html = "<div class='flex items-center gap-3'>" .
|
||||
"<img src='{$url}' class='rounded' style='width:60px;height:60px;object-fit:cover;' alt='{$fileName}' loading='lazy' />" .
|
||||
"<div class='flex flex-col'>" .
|
||||
"<span class='font-medium text-sm'>{$name}</span>" .
|
||||
"<span class='text-xs text-gray-500'>{$fileName}</span>" .
|
||||
"</div></div>";
|
||||
$html = "<div class='flex items-center gap-3'>".
|
||||
"<img src='{$url}' class='rounded' style='width:60px;height:60px;object-fit:cover;' alt='{$fileName}' loading='lazy' />".
|
||||
"<div class='flex flex-col'>".
|
||||
"<span class='font-medium text-sm'>{$name}</span>".
|
||||
"<span class='text-xs text-gray-500'>{$fileName}</span>".
|
||||
'</div></div>';
|
||||
|
||||
return [$url => $html];
|
||||
})->toArray();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Filament\Resources\TextWidgets\Schemas;
|
||||
|
||||
use App\Models\Category;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
|
@ -20,6 +22,14 @@ class TextWidgetForm
|
|||
Textarea::make('content')
|
||||
->rows(5)
|
||||
->columnSpanFull(),
|
||||
Select::make('category_id')
|
||||
->label('Category')
|
||||
->options(function () {
|
||||
return Category::all()
|
||||
->pluck('name', 'id')
|
||||
->toArray();
|
||||
})
|
||||
->searchable(),
|
||||
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\EntryResource;
|
||||
use App\Models\Entry;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -14,7 +15,7 @@ class EntryController extends Controller
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
return Entry::all();
|
||||
return EntryResource::collection(Entry::with('category')->get());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -24,7 +25,7 @@ class EntryController extends Controller
|
|||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user || $user->email !== config('app.admin_email')) {
|
||||
if (! $user || $user->email !== config('app.admin_email')) {
|
||||
return response()->json(['message' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
|
|
@ -35,13 +36,13 @@ class EntryController extends Controller
|
|||
|
||||
$validated['slug'] = $this->generateUniqueSlug($validated['title']);
|
||||
|
||||
return Entry::create($validated);
|
||||
return new EntryResource(Entry::create($validated));
|
||||
}
|
||||
|
||||
private function generateUniqueSlug(string $title): string
|
||||
{
|
||||
do {
|
||||
$slug = Str::slug($title) . '-' . Str::random(8);
|
||||
$slug = Str::slug($title).'-'.Str::random(8);
|
||||
} while (Entry::where('slug', $slug)->exists());
|
||||
|
||||
return $slug;
|
||||
|
|
@ -54,7 +55,7 @@ class EntryController extends Controller
|
|||
{
|
||||
$this->authorize('view', $entry);
|
||||
|
||||
return $entry;
|
||||
return new EntryResource($entry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -69,7 +70,7 @@ class EntryController extends Controller
|
|||
|
||||
$entry->update($validated);
|
||||
|
||||
return $entry;
|
||||
return new EntryResource($entry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\TextWidgetResource;
|
||||
use App\Models\TextWidget;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -13,7 +14,7 @@ class TextWidgetController extends Controller
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
return TextWidget::all();
|
||||
return TextWidget::with('category')->get()->map(fn ($tw) => new TextWidgetResource($tw));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -33,7 +34,7 @@ class TextWidgetController extends Controller
|
|||
'content' => 'required|string',
|
||||
]);
|
||||
|
||||
return TextWidget::create($validated);
|
||||
return new TextWidgetResource(TextWidget::create($validated));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,7 +42,7 @@ class TextWidgetController extends Controller
|
|||
*/
|
||||
public function show(TextWidget $textWidget)
|
||||
{
|
||||
return $textWidget;
|
||||
return new TextWidgetResource($textWidget->load('category'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -57,7 +58,7 @@ class TextWidgetController extends Controller
|
|||
|
||||
$textWidget->update($validated);
|
||||
|
||||
return $textWidget;
|
||||
return new TextWidgetResource($textWidget->load('category'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
32
app/Http/Resources/EntryResource.php
Normal file
32
app/Http/Resources/EntryResource.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class EntryResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'slug' => $this->slug,
|
||||
'description' => $this->description,
|
||||
'is_published' => $this->is_published,
|
||||
'is_featured' => $this->is_featured,
|
||||
'published_at' => $this->published_at,
|
||||
'content' => $this->content,
|
||||
'category' => $this->category->name ?? null,
|
||||
'featured_image_url' => $this->getFirstMediaUrl('featured-image') ?: null,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/Http/Resources/TextWidgetResource.php
Normal file
27
app/Http/Resources/TextWidgetResource.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TextWidgetResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'content' => $this->content,
|
||||
'category' => $this->category->name ?? null,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
10
app/Models/Category.php
Normal file
10
app/Models/Category.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
protected $fillable = ['name'];
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ use Filament\Forms\Components\RichEditor\Models\Concerns\InteractsWithRichConten
|
|||
use Filament\Forms\Components\RichEditor\Models\Contracts\HasRichContent;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
|
|
@ -16,11 +17,9 @@ use Spatie\Tags\HasTags;
|
|||
Entry model with rich content and media library integration
|
||||
This is the main article / blog rich content model
|
||||
*/
|
||||
class Entry extends Model implements HasRichContent, HasMedia
|
||||
|
||||
class Entry extends Model implements HasMedia, HasRichContent
|
||||
{
|
||||
|
||||
use InteractsWithMedia, InteractsWithRichContent, HasFactory, HasTags;
|
||||
use HasFactory, HasTags, InteractsWithMedia, InteractsWithRichContent;
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
|
|
@ -30,9 +29,9 @@ class Entry extends Model implements HasRichContent, HasMedia
|
|||
'is_featured',
|
||||
'published_at',
|
||||
'content',
|
||||
'category_id',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Set up rich content configuration for media library integration
|
||||
*/
|
||||
|
|
@ -56,4 +55,9 @@ class Entry extends Model implements HasRichContent, HasMedia
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Models;
|
|||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class TextWidget extends Model
|
||||
{
|
||||
|
|
@ -13,5 +14,11 @@ class TextWidget extends Model
|
|||
'title',
|
||||
'description',
|
||||
'content',
|
||||
'category_id',
|
||||
];
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('categories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('categories');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('text_widgets', function (Blueprint $table) {
|
||||
$table->foreignId('category_id')->nullable()->constrained('categories');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('text_widgets', function (Blueprint $table) {
|
||||
$table->dropForeign(['category_id']);
|
||||
$table->dropColumn('category_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('entries', function (Blueprint $table) {
|
||||
$table->foreignId('category_id')->nullable()->constrained('categories');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('entries', function (Blueprint $table) {
|
||||
$table->dropForeign(['category_id']);
|
||||
$table->dropColumn('category_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use App\Models\User;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ test('can list entries', function () {
|
|||
$response = $this->actingAs($user)->getJson('/api/entries');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonCount(3);
|
||||
->assertJsonCount(3, 'data');
|
||||
});
|
||||
|
||||
test('can create an entry', function () {
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ it('has correct fillable attributes', function () {
|
|||
'is_featured',
|
||||
'published_at',
|
||||
'content',
|
||||
'category_id',
|
||||
];
|
||||
|
||||
$entry = new Entry();
|
||||
$entry = new Entry;
|
||||
|
||||
expect($entry->getFillable())->toEqual($expected);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ it('has correct fillable attributes', function () {
|
|||
'title',
|
||||
'description',
|
||||
'content',
|
||||
'category_id',
|
||||
];
|
||||
|
||||
$entry = new TextWidget;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue