Initial commit: Laravel Blog API
This commit is contained in:
144
app/Http/Controllers/Api/CategoryController.php
Normal file
144
app/Http/Controllers/Api/CategoryController.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\CategoryRequest;
|
||||
use App\Http\Resources\CategoryResource;
|
||||
use App\Models\Category;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
/**
|
||||
* List categories.
|
||||
*
|
||||
* @group Categories
|
||||
*
|
||||
* @queryParam search string Filter categories by name. Example: tecnologia
|
||||
* @queryParam per_page integer Number of items per page. Default: 15. Example: 15
|
||||
*/
|
||||
public function index(Request $request): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$categories = Category::query()
|
||||
->withCount('posts')
|
||||
->when($request->filled('search'), function ($query) use ($request): void {
|
||||
$query->where('name', 'like', '%' . $request->string('search') . '%');
|
||||
})
|
||||
->orderBy('name')
|
||||
->paginate((int) $request->integer('per_page', 15));
|
||||
|
||||
return CategoryResource::collection($categories);
|
||||
} catch (Throwable $throwable) {
|
||||
Log::error('Failed to list categories.', ['exception' => $throwable]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Não foi possível listar as categorias.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create category.
|
||||
*
|
||||
* @group Categories
|
||||
*
|
||||
* @bodyParam name string required Category name. Example: Tecnologia
|
||||
* @bodyParam slug string Optional custom slug. Example: tecnologia
|
||||
* @bodyParam description string Category description. Example: Notícias sobre tecnologia e inovação.
|
||||
*/
|
||||
public function store(CategoryRequest $request): CategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$data = $request->validated();
|
||||
$category = Category::query()->create($data);
|
||||
|
||||
return (new CategoryResource($category))
|
||||
->additional(['message' => 'Categoria criada com sucesso.'])
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
} catch (Throwable $throwable) {
|
||||
Log::error('Failed to create category.', ['exception' => $throwable]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Não foi possível criar a categoria.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show category.
|
||||
*
|
||||
* @group Categories
|
||||
*/
|
||||
public function show(Category $category): CategoryResource
|
||||
{
|
||||
$category->loadCount('posts');
|
||||
|
||||
return new CategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update category.
|
||||
*
|
||||
* @group Categories
|
||||
*
|
||||
* @bodyParam name string required Category name. Example: Tecnologia
|
||||
* @bodyParam slug string Optional custom slug. Example: tecnologia
|
||||
* @bodyParam description string Category description. Example: Notícias sobre tecnologia e inovação.
|
||||
*/
|
||||
public function update(CategoryRequest $request, Category $category): CategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$data = $request->validated();
|
||||
|
||||
if ($request->filled('name') && ! $request->filled('slug')) {
|
||||
$data['slug'] = Category::generateUniqueSlug($data['name'], $category->id);
|
||||
}
|
||||
|
||||
$category->update($data);
|
||||
|
||||
return (new CategoryResource($category->refresh()->loadCount('posts')))
|
||||
->additional(['message' => 'Categoria atualizada com sucesso.']);
|
||||
} catch (Throwable $throwable) {
|
||||
Log::error('Failed to update category.', ['category_id' => $category->id, 'exception' => $throwable]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Não foi possível atualizar a categoria.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete category.
|
||||
*
|
||||
* @group Categories
|
||||
*/
|
||||
public function destroy(Category $category): JsonResponse
|
||||
{
|
||||
try {
|
||||
if ($category->posts()->exists()) {
|
||||
return response()->json([
|
||||
'message' => 'Não é possível remover uma categoria vinculada a posts.',
|
||||
], 409);
|
||||
}
|
||||
|
||||
$category->delete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Categoria removida com sucesso.',
|
||||
]);
|
||||
} catch (Throwable $throwable) {
|
||||
Log::error('Failed to delete category.', ['category_id' => $category->id, 'exception' => $throwable]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Não foi possível remover a categoria.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
171
app/Http/Controllers/Api/PostController.php
Normal file
171
app/Http/Controllers/Api/PostController.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\PostRequest;
|
||||
use App\Http\Resources\PostResource;
|
||||
use App\Models\Post;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
class PostController extends Controller
|
||||
{
|
||||
/**
|
||||
* List posts.
|
||||
*
|
||||
* @group Posts
|
||||
*
|
||||
* @queryParam search string Filter posts by title. Example: Laravel
|
||||
* @queryParam category_id integer Filter posts by category ID. Example: 1
|
||||
* @queryParam category_slug string Filter posts by category slug. Example: tecnologia
|
||||
* @queryParam is_featured boolean Filter featured posts. Example: true
|
||||
* @queryParam status string Filter by status: draft or published. Example: published
|
||||
* @queryParam per_page integer Number of items per page. Default: 15. Example: 15
|
||||
*/
|
||||
public function index(Request $request): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$posts = Post::query()
|
||||
->with('category')
|
||||
->when($request->filled('search'), function ($query) use ($request): void {
|
||||
$query->where('title', 'like', '%' . $request->string('search') . '%');
|
||||
})
|
||||
->when($request->filled('category_id'), function ($query) use ($request): void {
|
||||
$query->where('category_id', $request->integer('category_id'));
|
||||
})
|
||||
->when($request->filled('category_slug'), function ($query) use ($request): void {
|
||||
$query->whereHas('category', function ($categoryQuery) use ($request): void {
|
||||
$categoryQuery->where('slug', $request->string('category_slug'));
|
||||
});
|
||||
})
|
||||
->when($request->has('is_featured'), function ($query) use ($request): void {
|
||||
$query->where('is_featured', $request->boolean('is_featured'));
|
||||
})
|
||||
->when($request->filled('status'), function ($query) use ($request): void {
|
||||
$query->where('status', $request->string('status'));
|
||||
})
|
||||
->orderByDesc('published_at')
|
||||
->orderByDesc('created_at')
|
||||
->paginate((int) $request->integer('per_page', 15));
|
||||
|
||||
return PostResource::collection($posts);
|
||||
} catch (Throwable $throwable) {
|
||||
Log::error('Failed to list posts.', ['exception' => $throwable]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Não foi possível listar os posts.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create post.
|
||||
*
|
||||
* @group Posts
|
||||
*
|
||||
* @bodyParam title string required Post title. Example: Laravel 12 em Microsserviços
|
||||
* @bodyParam slug string Optional custom slug. Example: laravel-12-em-microsservicos
|
||||
* @bodyParam content string required Full post content. Example: Conteúdo completo da notícia.
|
||||
* @bodyParam excerpt string Short post excerpt. Example: Resumo da notícia.
|
||||
* @bodyParam category_id integer required Category ID. Example: 1
|
||||
* @bodyParam featured_image string URL of featured image. Example: https://cdn.example.com/image.jpg
|
||||
* @bodyParam is_featured boolean Whether the post is featured. Example: true
|
||||
* @bodyParam status string required draft or published. Example: published
|
||||
* @bodyParam published_at datetime Publication date. Example: 2026-05-28 10:00:00
|
||||
*/
|
||||
public function store(PostRequest $request): PostResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$data = $request->validated();
|
||||
$post = Post::query()->create($data);
|
||||
$post->load('category');
|
||||
|
||||
return (new PostResource($post))
|
||||
->additional(['message' => 'Post criado com sucesso.'])
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
} catch (Throwable $throwable) {
|
||||
Log::error('Failed to create post.', ['exception' => $throwable]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Não foi possível criar o post.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show post.
|
||||
*
|
||||
* @group Posts
|
||||
*/
|
||||
public function show(Post $post): PostResource
|
||||
{
|
||||
$post->load('category');
|
||||
|
||||
return new PostResource($post);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update post.
|
||||
*
|
||||
* @group Posts
|
||||
*
|
||||
* @bodyParam title string required Post title. Example: Laravel 12 em Microsserviços
|
||||
* @bodyParam slug string Optional custom slug. Example: laravel-12-em-microsservicos
|
||||
* @bodyParam content string required Full post content. Example: Conteúdo completo da notícia.
|
||||
* @bodyParam excerpt string Short post excerpt. Example: Resumo da notícia.
|
||||
* @bodyParam category_id integer required Category ID. Example: 1
|
||||
* @bodyParam featured_image string URL of featured image. Example: https://cdn.example.com/image.jpg
|
||||
* @bodyParam is_featured boolean Whether the post is featured. Example: true
|
||||
* @bodyParam status string required draft or published. Example: published
|
||||
* @bodyParam published_at datetime Publication date. Example: 2026-05-28 10:00:00
|
||||
*/
|
||||
public function update(PostRequest $request, Post $post): PostResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$data = $request->validated();
|
||||
|
||||
if ($request->filled('title') && ! $request->filled('slug')) {
|
||||
$data['slug'] = Post::generateUniqueSlug($data['title'], $post->id);
|
||||
}
|
||||
|
||||
$post->update($data);
|
||||
$post->refresh()->load('category');
|
||||
|
||||
return (new PostResource($post))
|
||||
->additional(['message' => 'Post atualizado com sucesso.']);
|
||||
} catch (Throwable $throwable) {
|
||||
Log::error('Failed to update post.', ['post_id' => $post->id, 'exception' => $throwable]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Não foi possível atualizar o post.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete post.
|
||||
*
|
||||
* @group Posts
|
||||
*/
|
||||
public function destroy(Post $post): JsonResponse
|
||||
{
|
||||
try {
|
||||
$post->delete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Post removido com sucesso.',
|
||||
]);
|
||||
} catch (Throwable $throwable) {
|
||||
Log::error('Failed to delete post.', ['post_id' => $post->id, 'exception' => $throwable]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Não foi possível remover o post.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
54
app/Http/Requests/CategoryRequest.php
Normal file
54
app/Http/Requests/CategoryRequest.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CategoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$categoryId = $this->route('category')?->id;
|
||||
$nameRule = $this->isMethod('post') ? 'required' : 'sometimes';
|
||||
|
||||
return [
|
||||
'name' => [$nameRule, 'string', 'max:255'],
|
||||
'slug' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'max:255',
|
||||
'alpha_dash:ascii',
|
||||
Rule::unique('categories', 'slug')->ignore($categoryId),
|
||||
],
|
||||
'description' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'nome',
|
||||
'slug' => 'slug',
|
||||
'description' => 'descrição',
|
||||
];
|
||||
}
|
||||
}
|
||||
86
app/Http/Requests/PostRequest.php
Normal file
86
app/Http/Requests/PostRequest.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Post;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PostRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$postId = $this->route('post')?->id;
|
||||
$requiredOnCreate = $this->isMethod('post') ? 'required' : 'sometimes';
|
||||
|
||||
return [
|
||||
'title' => [$requiredOnCreate, 'string', 'max:255'],
|
||||
'slug' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'max:255',
|
||||
'alpha_dash:ascii',
|
||||
Rule::unique('posts', 'slug')->ignore($postId),
|
||||
],
|
||||
'content' => [$requiredOnCreate, 'string'],
|
||||
'excerpt' => ['nullable', 'string', 'max:1000'],
|
||||
'category_id' => [$requiredOnCreate, 'integer', 'exists:categories,id'],
|
||||
'featured_image' => ['nullable', 'url', 'max:2048'],
|
||||
'is_featured' => ['sometimes', 'boolean'],
|
||||
'status' => [$requiredOnCreate, Rule::in([Post::STATUS_DRAFT, Post::STATUS_PUBLISHED])],
|
||||
'published_at' => ['nullable', 'date'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator): void {
|
||||
$post = $this->route('post');
|
||||
$status = $this->input('status', $post?->status);
|
||||
$publishedAt = $this->input('published_at', $post?->published_at);
|
||||
|
||||
if ($status === Post::STATUS_PUBLISHED && blank($publishedAt)) {
|
||||
$validator->errors()->add(
|
||||
'published_at',
|
||||
'A data de publicação é obrigatória quando o status do post é published.'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'título',
|
||||
'slug' => 'slug',
|
||||
'content' => 'conteúdo',
|
||||
'excerpt' => 'resumo',
|
||||
'category_id' => 'categoria',
|
||||
'featured_image' => 'imagem destacada',
|
||||
'is_featured' => 'post em destaque',
|
||||
'status' => 'status',
|
||||
'published_at' => 'data de publicação',
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/Http/Resources/CategoryResource.php
Normal file
27
app/Http/Resources/CategoryResource.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class CategoryResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'slug' => $this->slug,
|
||||
'description' => $this->description,
|
||||
'posts_count' => $this->whenCounted('posts'),
|
||||
'created_at' => $this->created_at?->toISOString(),
|
||||
'updated_at' => $this->updated_at?->toISOString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
32
app/Http/Resources/PostResource.php
Normal file
32
app/Http/Resources/PostResource.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class PostResource 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,
|
||||
'content' => $this->content,
|
||||
'excerpt' => $this->excerpt,
|
||||
'featured_image' => $this->featured_image,
|
||||
'is_featured' => (bool) $this->is_featured,
|
||||
'status' => $this->status,
|
||||
'published_at' => $this->published_at?->toISOString(),
|
||||
'category' => new CategoryResource($this->whenLoaded('category')),
|
||||
'created_at' => $this->created_at?->toISOString(),
|
||||
'updated_at' => $this->updated_at?->toISOString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
70
app/Models/Category.php
Normal file
70
app/Models/Category.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* Boot the model and register model events.
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (Category $category): void {
|
||||
if (blank($category->slug)) {
|
||||
$category->slug = static::generateUniqueSlug($category->name);
|
||||
}
|
||||
});
|
||||
|
||||
static::updating(function (Category $category): void {
|
||||
if ($category->isDirty('name') && blank($category->slug)) {
|
||||
$category->slug = static::generateUniqueSlug($category->name, $category->id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the posts associated with the category.
|
||||
*/
|
||||
public function posts(): HasMany
|
||||
{
|
||||
return $this->hasMany(Post::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique slug based on the category name.
|
||||
*/
|
||||
public static function generateUniqueSlug(string $name, ?int $ignoreId = null): string
|
||||
{
|
||||
$baseSlug = Str::slug($name);
|
||||
$slug = $baseSlug;
|
||||
$counter = 1;
|
||||
|
||||
while (static::query()
|
||||
->where('slug', $slug)
|
||||
->when($ignoreId, fn ($query) => $query->whereKeyNot($ignoreId))
|
||||
->exists()) {
|
||||
$slug = "{$baseSlug}-{$counter}";
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
}
|
||||
100
app/Models/Post.php
Normal file
100
app/Models/Post.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Post extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const STATUS_DRAFT = 'draft';
|
||||
public const STATUS_PUBLISHED = 'published';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'slug',
|
||||
'content',
|
||||
'excerpt',
|
||||
'category_id',
|
||||
'featured_image',
|
||||
'is_featured',
|
||||
'status',
|
||||
'published_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_featured' => 'boolean',
|
||||
'published_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot the model and register model events.
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (Post $post): void {
|
||||
if (blank($post->slug)) {
|
||||
$post->slug = static::generateUniqueSlug($post->title);
|
||||
}
|
||||
});
|
||||
|
||||
static::updating(function (Post $post): void {
|
||||
if ($post->isDirty('title') && blank($post->slug)) {
|
||||
$post->slug = static::generateUniqueSlug($post->title, $post->id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the category that owns the post.
|
||||
*/
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique slug based on the post title.
|
||||
*/
|
||||
public static function generateUniqueSlug(string $title, ?int $ignoreId = null): string
|
||||
{
|
||||
$baseSlug = Str::slug($title);
|
||||
$slug = $baseSlug;
|
||||
$counter = 1;
|
||||
|
||||
while (static::query()
|
||||
->where('slug', $slug)
|
||||
->when($ignoreId, fn ($query) => $query->whereKeyNot($ignoreId))
|
||||
->exists()) {
|
||||
$slug = "{$baseSlug}-{$counter}";
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the post is published.
|
||||
*/
|
||||
public function isPublished(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_PUBLISHED;
|
||||
}
|
||||
}
|
||||
49
app/Models/User.php
Normal file
49
app/Models/User.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
24
app/Providers/AppServiceProvider.php
Normal file
24
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user