feat(emoji): implement comprehensive emoji management system

Add complete emoji management functionality with API and web interfaces

Features:
* Emoji management with categories, aliases, and search capabilities
* Full REST API with CRUD operations for emojis, categories, and aliases
* Laravel Scout integration for emoji search functionality
* Sprite support for emoji display optimization
* Hierarchical emoji categorization system
* Multiple alias support per emoji for enhanced discoverability

Technical Implementation:
* Models: Emoji, EmojiCategory, EmojiAlias with proper relationships
* Controllers: API and web controllers with resource routing
* Validation: Form request classes for data integrity
* Authorization: Policy classes for access control
* Resources: API resource classes for consistent JSON responses
* Database: Migrations with foreign key constraints and indexes
* Testing: Model factories and seeders for development/testing

API Features:
* RESTful endpoints: /api/emoji/{emojis,categories,aliases}
* Search endpoints with Laravel Scout integration
* Pagination and filtering capabilities
* Relationship eager loading via query parameters
* Authentication via Laravel Sanctum

Documentation:
* Complete API documentation with examples and response formats
* Model relationship documentation
* Development setup and contribution guidelines

Database Schema:
* emoji_categories: id, title, description, display_order
* emojis: id, title, emoji_text, emoji_shortcode, image_url, sprite_mode, sprite_params, category_id, display_order
* emoji_aliases: id, alias, emoji_id

Files Added:
* 8 controllers (API + web interfaces)
* 6 form request validation classes
* 3 API resource classes
* 3 model classes with relationships
* 3 authorization policy classes
* 3 model factories for testing
* 3 database migrations
* 3 database seeders
* 9 documentation files
* Updated API routes
This commit is contained in:
Yury Pikhtarev 2025-07-02 02:14:40 +04:00
commit c8656d7576
No known key found for this signature in database
47 changed files with 3545 additions and 7 deletions

View file

@ -0,0 +1,114 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Emoji\StoreEmojiAliasRequest;
use App\Http\Requests\Emoji\UpdateEmojiAliasRequest;
use App\Http\Resources\EmojiAliasResource;
use App\Models\EmojiAlias;
use Illuminate\Http\Request;
class EmojiAliasController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$aliases = EmojiAlias::query()
->when($request->get('emoji_id'), function ($query, $emojiId) {
$query->where('emoji_id', $emojiId);
})
->when($request->get('search'), function ($query, $search) {
$query->where('alias', 'like', "%{$search}%");
})
->when($request->get('with_emoji'), function ($query) {
$query->with(['emoji' => function ($q) {
$q->with('category');
}]);
})
->orderBy('alias')
->paginate($request->get('per_page', 50));
return EmojiAliasResource::collection($aliases);
}
/**
* Store a newly created resource in storage.
*/
public function store(StoreEmojiAliasRequest $request)
{
$alias = EmojiAlias::create($request->validated());
if ($request->get('with_emoji')) {
$alias->load(['emoji' => function ($query) {
$query->with('category');
}]);
}
return new EmojiAliasResource($alias);
}
/**
* Display the specified resource.
*/
public function show(EmojiAlias $emojiAlias, Request $request)
{
$emojiAlias->load(['emoji' => function ($query) {
$query->with('category');
}]);
return new EmojiAliasResource($emojiAlias);
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateEmojiAliasRequest $request, EmojiAlias $emojiAlias)
{
$emojiAlias->update($request->validated());
if ($request->get('with_emoji')) {
$emojiAlias->load(['emoji' => function ($query) {
$query->with('category');
}]);
}
return new EmojiAliasResource($emojiAlias);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(EmojiAlias $emojiAlias)
{
$emojiAlias->delete();
return response()->json(null, 204);
}
/**
* Search aliases using Laravel Scout.
*/
public function search(Request $request)
{
$request->validate([
'q' => 'required|string|min:1',
'limit' => 'integer|min:1|max:100',
]);
$aliases = EmojiAlias::search($request->get('q'))
->take($request->get('limit', 20))
->get();
// Load emoji relationships if requested
if ($request->get('with_emoji')) {
$aliases->load(['emoji' => function ($query) {
$query->with('category');
}]);
}
return EmojiAliasResource::collection($aliases);
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Emoji\StoreEmojiCategoryRequest;
use App\Http\Requests\Emoji\UpdateEmojiCategoryRequest;
use App\Http\Resources\EmojiCategoryResource;
use App\Models\EmojiCategory;
use Illuminate\Http\Request;
class EmojiCategoryController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$categories = EmojiCategory::query()
->when($request->get('with_emojis'), function ($query) {
$query->withCount('emojis');
})
->when($request->get('include_emojis'), function ($query) {
$query->with(['emojis' => function ($q) {
$q->orderBy('display_order');
}]);
})
->orderBy('display_order')
->get();
return EmojiCategoryResource::collection($categories);
}
/**
* Store a newly created resource in storage.
*/
public function store(StoreEmojiCategoryRequest $request)
{
$category = EmojiCategory::create($request->validated());
return new EmojiCategoryResource($category);
}
/**
* Display the specified resource.
*/
public function show(EmojiCategory $emojiCategory, Request $request)
{
$emojiCategory->load([
'emojis' => function ($query) use ($request) {
$query->orderBy('display_order');
if ($request->get('with_aliases')) {
$query->with('aliases');
}
},
]);
return new EmojiCategoryResource($emojiCategory);
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateEmojiCategoryRequest $request, EmojiCategory $emojiCategory)
{
$emojiCategory->update($request->validated());
return new EmojiCategoryResource($emojiCategory);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(EmojiCategory $emojiCategory)
{
$emojiCategory->delete();
return response()->json(null, 204);
}
}

View file

@ -0,0 +1,119 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Emoji\StoreEmojiRequest;
use App\Http\Requests\Emoji\UpdateEmojiRequest;
use App\Http\Resources\EmojiResource;
use App\Models\Emoji;
use Illuminate\Http\Request;
class EmojiController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$emojis = Emoji::query()
->when($request->get('category_id'), function ($query, $categoryId) {
$query->where('emoji_category_id', $categoryId);
})
->when($request->get('search'), function ($query, $search) {
$query->where(function ($q) use ($search) {
$q->where('emoji_shortcode', 'like', "%{$search}%")
->orWhere('title', 'like', "%{$search}%")
->orWhere('emoji_text', 'like', "%{$search}%");
});
})
->when($request->get('with_category'), function ($query) {
$query->with('category');
})
->when($request->get('with_aliases'), function ($query) {
$query->with('aliases');
})
->orderBy('display_order')
->paginate($request->get('per_page', 50));
return EmojiResource::collection($emojis);
}
/**
* Store a newly created resource in storage.
*/
public function store(StoreEmojiRequest $request)
{
$emoji = Emoji::create($request->validated());
if ($request->get('with_category')) {
$emoji->load('category');
}
return new EmojiResource($emoji);
}
/**
* Display the specified resource.
*/
public function show(Emoji $emoji, Request $request)
{
$emoji->load([
'category',
'aliases' => function ($query) {
$query->orderBy('alias');
},
]);
return new EmojiResource($emoji);
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateEmojiRequest $request, Emoji $emoji)
{
$emoji->update($request->validated());
if ($request->get('with_category')) {
$emoji->load('category');
}
return new EmojiResource($emoji);
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Emoji $emoji)
{
$emoji->delete();
return response()->json(null, 204);
}
/**
* Search emojis using Laravel Scout.
*/
public function search(Request $request)
{
$request->validate([
'q' => 'required|string|min:1',
'limit' => 'integer|min:1|max:100',
]);
$emojis = Emoji::search($request->get('q'))
->take($request->get('limit', 20))
->get();
// Load relationships if requested
if ($request->get('with_category')) {
$emojis->load('category');
}
if ($request->get('with_aliases')) {
$emojis->load('aliases');
}
return EmojiResource::collection($emojis);
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Emoji;
use App\Http\Controllers\Controller;
use App\Http\Requests\Emoji\StoreEmojiAliasRequest;
use App\Http\Requests\Emoji\UpdateEmojiAliasRequest;
use App\Models\EmojiAlias;
class EmojiAliasController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(StoreEmojiAliasRequest $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(EmojiAlias $emojiAlias)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(EmojiAlias $emojiAlias)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateEmojiAliasRequest $request, EmojiAlias $emojiAlias)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(EmojiAlias $emojiAlias)
{
//
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Emoji;
use App\Http\Controllers\Controller;
use App\Http\Requests\Emoji\StoreEmojiCategoryRequest;
use App\Http\Requests\Emoji\UpdateEmojiCategoryRequest;
use App\Models\EmojiCategory;
class EmojiCategoryController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(StoreEmojiCategoryRequest $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(EmojiCategory $emojiCategory)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(EmojiCategory $emojiCategory)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateEmojiCategoryRequest $request, EmojiCategory $emojiCategory)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(EmojiCategory $emojiCategory)
{
//
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Emoji;
use App\Http\Controllers\Controller;
use App\Http\Requests\Emoji\StoreEmojiRequest;
use App\Http\Requests\Emoji\UpdateEmojiRequest;
use App\Models\Emoji;
class EmojiController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(StoreEmojiRequest $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(Emoji $emoji)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Emoji $emoji)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateEmojiRequest $request, Emoji $emoji)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Emoji $emoji)
{
//
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace App\Http\Requests\Emoji;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreEmojiAliasRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // TODO: Add proper authorization logic
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'emoji_id' => 'required|exists:emojis,id',
'alias' => [
'required',
'string',
'max:255',
'regex:/^:[a-zA-Z0-9_-]+:$/',
'unique:emoji_aliases,alias',
Rule::notIn(\App\Models\Emoji::pluck('emoji_shortcode')->toArray()),
],
];
}
/**
* Get custom validation messages.
*/
public function messages(): array
{
return [
'alias.regex' => 'The alias must be in the format :name: (e.g., :happy:)',
'alias.unique' => 'This alias is already taken.',
'alias.not_in' => 'This alias conflicts with an existing emoji shortcode.',
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Emoji;
use Illuminate\Foundation\Http\FormRequest;
class StoreEmojiCategoryRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // TODO: Add proper authorization logic
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'display_order' => 'required|integer|min:0',
];
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace App\Http\Requests\Emoji;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreEmojiRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // TODO: Add proper authorization logic
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'emoji_text' => 'nullable|string|max:10',
'emoji_shortcode' => [
'required',
'string',
'max:255',
'regex:/^:[a-zA-Z0-9_-]+:$/',
'unique:emojis,emoji_shortcode',
Rule::notIn(\App\Models\EmojiAlias::pluck('alias')->toArray()),
],
'image_url' => 'nullable|string|max:500',
'sprite_mode' => 'boolean',
'sprite_params' => 'nullable|array',
'sprite_params.x' => 'required_if:sprite_mode,true|integer|min:0',
'sprite_params.y' => 'required_if:sprite_mode,true|integer|min:0',
'sprite_params.width' => 'required_if:sprite_mode,true|integer|min:1',
'sprite_params.height' => 'required_if:sprite_mode,true|integer|min:1',
'sprite_params.sheet' => 'required_if:sprite_mode,true|string|max:255',
'emoji_category_id' => 'nullable|exists:emoji_categories,id',
'display_order' => 'required|integer|min:0',
];
}
/**
* Get custom validation messages.
*/
public function messages(): array
{
return [
'emoji_shortcode.regex' => 'The emoji shortcode must be in the format :name: (e.g., :smile:)',
'emoji_shortcode.unique' => 'This shortcode is already taken.',
'emoji_shortcode.not_in' => 'This shortcode conflicts with an existing alias.',
];
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Http\Requests\Emoji;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class UpdateEmojiAliasRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // TODO: Add proper authorization logic
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
$aliasId = $this->route('emoji_alias')->id;
return [
'emoji_id' => 'sometimes|exists:emojis,id',
'alias' => [
'sometimes',
'string',
'max:255',
'regex:/^:[a-zA-Z0-9_-]+:$/',
Rule::unique('emoji_aliases', 'alias')->ignore($aliasId),
Rule::notIn(\App\Models\Emoji::pluck('emoji_shortcode')->toArray()),
],
];
}
/**
* Get custom validation messages.
*/
public function messages(): array
{
return [
'alias.regex' => 'The alias must be in the format :name: (e.g., :happy:)',
'alias.unique' => 'This alias is already taken.',
'alias.not_in' => 'This alias conflicts with an existing emoji shortcode.',
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Emoji;
use Illuminate\Foundation\Http\FormRequest;
class UpdateEmojiCategoryRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // TODO: Add proper authorization logic
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'title' => 'sometimes|string|max:255',
'display_order' => 'sometimes|integer|min:0',
];
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace App\Http\Requests\Emoji;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class UpdateEmojiRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; // TODO: Add proper authorization logic
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
$emojiId = $this->route('emoji')->id;
return [
'title' => 'sometimes|string|max:255',
'emoji_text' => 'nullable|string|max:10',
'emoji_shortcode' => [
'sometimes',
'string',
'max:255',
'regex:/^:[a-zA-Z0-9_-]+:$/',
Rule::unique('emojis', 'emoji_shortcode')->ignore($emojiId),
Rule::notIn(\App\Models\EmojiAlias::pluck('alias')->toArray()),
],
'image_url' => 'nullable|string|max:500',
'sprite_mode' => 'sometimes|boolean',
'sprite_params' => 'nullable|array',
'sprite_params.x' => 'required_if:sprite_mode,true|integer|min:0',
'sprite_params.y' => 'required_if:sprite_mode,true|integer|min:0',
'sprite_params.width' => 'required_if:sprite_mode,true|integer|min:1',
'sprite_params.height' => 'required_if:sprite_mode,true|integer|min:1',
'sprite_params.sheet' => 'required_if:sprite_mode,true|string|max:255',
'emoji_category_id' => 'nullable|exists:emoji_categories,id',
'display_order' => 'sometimes|integer|min:0',
];
}
/**
* Get custom validation messages.
*/
public function messages(): array
{
return [
'emoji_shortcode.regex' => 'The emoji shortcode must be in the format :name: (e.g., :smile:)',
'emoji_shortcode.unique' => 'This shortcode is already taken.',
'emoji_shortcode.not_in' => 'This shortcode conflicts with an existing alias.',
];
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class EmojiAliasResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'alias' => $this->alias,
'emoji' => new EmojiResource($this->whenLoaded('emoji')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class EmojiCategoryResource 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,
'display_order' => $this->display_order,
'emojis_count' => $this->whenCounted('emojis'),
'emojis' => EmojiResource::collection($this->whenLoaded('emojis')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class EmojiResource 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,
'emoji_text' => $this->emoji_text,
'emoji_shortcode' => $this->emoji_shortcode,
'image_url' => $this->image_url,
'sprite_mode' => $this->sprite_mode,
'sprite_params' => $this->sprite_params,
'display_order' => $this->display_order,
'category' => new EmojiCategoryResource($this->whenLoaded('category')),
'aliases' => EmojiAliasResource::collection($this->whenLoaded('aliases')),
'aliases_count' => $this->whenCounted('aliases'),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

83
app/Models/Emoji.php Normal file
View file

@ -0,0 +1,83 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Emoji extends Model
{
/** @use HasFactory<\Database\Factories\EmojiFactory> */
use HasFactory, Searchable;
/**
* The table associated with the model.
*
* Note: Explicitly set because Laravel's pluralization doesn't handle "emoji" correctly
* (it's a Japanese loanword where singular and plural forms are the same).
*
* @var string
*/
protected $table = 'emojis';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'title',
'emoji_text',
'emoji_shortcode',
'image_url',
'sprite_mode',
'sprite_params',
'emoji_category_id',
'display_order',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'sprite_mode' => 'boolean',
'sprite_params' => 'array',
];
/**
* Get the category that owns the emoji.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<EmojiCategory>
*/
public function category()
{
return $this->belongsTo(EmojiCategory::class, 'emoji_category_id');
}
/**
* Get the aliases for the emoji.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany<EmojiAlias>
*/
public function aliases()
{
return $this->hasMany(EmojiAlias::class);
}
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'emoji_shortcode' => $this->emoji_shortcode,
'emoji_text' => $this->emoji_text,
];
}
}

47
app/Models/EmojiAlias.php Normal file
View file

@ -0,0 +1,47 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class EmojiAlias extends Model
{
/** @use HasFactory<\Database\Factories\EmojiAliasFactory> */
use HasFactory, Searchable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'emoji_id',
'alias',
];
/**
* Get the emoji that owns the alias.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<Emoji>
*/
public function emoji()
{
return $this->belongsTo(Emoji::class);
}
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'alias' => $this->alias,
'emoji_id' => $this->emoji_id,
];
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class EmojiCategory extends Model
{
/** @use HasFactory<\Database\Factories\EmojiCategoryFactory> */
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'title',
'display_order',
];
/**
* Get the emojis for the category.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany<Emoji>
*/
public function emojis()
{
return $this->hasMany(Emoji::class);
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\EmojiAlias;
use App\Models\User;
class EmojiAliasPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, EmojiAlias $emojiAlias): bool
{
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, EmojiAlias $emojiAlias): bool
{
return false;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, EmojiAlias $emojiAlias): bool
{
return false;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, EmojiAlias $emojiAlias): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, EmojiAlias $emojiAlias): bool
{
return false;
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\EmojiCategory;
use App\Models\User;
class EmojiCategoryPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, EmojiCategory $emojiCategory): bool
{
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, EmojiCategory $emojiCategory): bool
{
return false;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, EmojiCategory $emojiCategory): bool
{
return false;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, EmojiCategory $emojiCategory): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, EmojiCategory $emojiCategory): bool
{
return false;
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace App\Policies;
use App\Models\Emoji;
use App\Models\User;
class EmojiPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Emoji $emoji): bool
{
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return false;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Emoji $emoji): bool
{
return false;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Emoji $emoji): bool
{
return false;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Emoji $emoji): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Emoji $emoji): bool
{
return false;
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Database\Factories;
use App\Models\Emoji;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\EmojiAlias>
*/
class EmojiAliasFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'emoji_id' => Emoji::factory(),
'alias' => ':' . $this->faker->unique()->word() . ':',
];
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\EmojiCategory>
*/
class EmojiCategoryFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'title' => $this->faker->randomElement(['Smileys & Emotion', 'People & Body', 'Animals & Nature', 'Food & Drink', 'Travel & Places', 'Activities', 'Objects', 'Symbols', 'Flags']),
'display_order' => $this->faker->numberBetween(1, 100),
];
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace Database\Factories;
use App\Models\EmojiCategory;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Emoji>
*/
class EmojiFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$emojis = ['😊', '😃', '😄', '😁', '😆', '😅', '🤣', '😂', '🙂', '🙃', '😉', '😊', '😇', '🥰', '😍', '🤩', '😘', '😗', '😚', '😙', '🥲', '😋', '😛', '😜', '🤪', '😝', '🤑', '🤗', '🤭', '🤫', '🤔'];
$hasEmoji = $this->faker->boolean(80); // 80% chance of having unicode emoji
return [
'title' => $this->faker->words(2, true),
'emoji_text' => $hasEmoji ? $this->faker->randomElement($emojis) : null,
'emoji_shortcode' => ':' . $this->faker->unique()->word() . ':',
'image_url' => !$hasEmoji ? '/emojis/custom/' . $this->faker->unique()->word() . '.png' : null,
'sprite_mode' => false,
'sprite_params' => null,
'emoji_category_id' => EmojiCategory::factory(),
'display_order' => $this->faker->numberBetween(1, 100),
];
}
/**
* Indicate that the emoji uses sprite mode.
*/
public function withSprite(): static
{
return $this->state(fn (array $attributes) => [
'sprite_mode' => true,
'sprite_params' => [
'x' => $this->faker->numberBetween(0, 500),
'y' => $this->faker->numberBetween(0, 500),
'width' => 32,
'height' => 32,
'sheet' => 'emoji-sheet-' . $this->faker->numberBetween(1, 5) . '.png',
],
'image_url' => null,
'emoji_text' => null,
]);
}
/**
* Indicate that the emoji is a custom image.
*/
public function customImage(): static
{
return $this->state(fn (array $attributes) => [
'emoji_text' => null,
'image_url' => '/emojis/custom/' . $this->faker->unique()->word() . '.png',
]);
}
}

View file

@ -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::create('emoji_categories', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->integer('display_order')->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('emoji_categories');
}
};

View file

@ -0,0 +1,35 @@
<?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('emojis', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('emoji_text')->nullable();
$table->string('emoji_shortcode')->unique();
$table->string('image_url')->nullable();
$table->boolean('sprite_mode')->default(false);
$table->json('sprite_params')->nullable();
$table->foreignId('emoji_category_id')->nullable()->constrained('emoji_categories')->nullOnDelete();
$table->integer('display_order')->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('emojis');
}
};

View file

@ -0,0 +1,32 @@
<?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('emoji_aliases', function (Blueprint $table) {
$table->id();
$table->foreignId('emoji_id')->constrained('emojis')->cascadeOnDelete();
$table->string('alias')->unique();
$table->timestamps();
// Composite index for performance when joining
$table->index(['emoji_id', 'alias']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('emoji_aliases');
}
};

View file

@ -0,0 +1,16 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class EmojiAliasSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class EmojiCategorySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View file

@ -0,0 +1,124 @@
<?php
namespace Database\Seeders;
use App\Models\Emoji;
use App\Models\EmojiAlias;
use App\Models\EmojiCategory;
use Illuminate\Database\Seeder;
class EmojiSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Create categories
$categories = [
'Smileys' => ['title' => 'Smileys', 'display_order' => 1],
'People' => ['title' => 'People', 'display_order' => 2],
'Nature' => ['title' => 'Nature', 'display_order' => 3],
'Food' => ['title' => 'Food', 'display_order' => 4],
'Objects' => ['title' => 'Objects', 'display_order' => 5],
];
$categoryModels = [];
foreach ($categories as $key => $data) {
$categoryModels[$key] = EmojiCategory::create($data);
}
// Define emojis with their categories and aliases
$emojis = [
// Smileys
[
'category' => 'Smileys',
'emojis' => [
['title' => 'Grinning', 'emoji_text' => '😀', 'emoji_shortcode' => ':grinning:', 'aliases' => [':grin:']],
['title' => 'Smile', 'emoji_text' => '😊', 'emoji_shortcode' => ':smile:', 'aliases' => [':blush:', ':happy:']],
['title' => 'Laughing', 'emoji_text' => '😂', 'emoji_shortcode' => ':joy:', 'aliases' => [':laughing:', ':lol:']],
['title' => 'Wink', 'emoji_text' => '😉', 'emoji_shortcode' => ':wink:', 'aliases' => [';)', ':winking:']],
['title' => 'Heart Eyes', 'emoji_text' => '😍', 'emoji_shortcode' => ':heart_eyes:', 'aliases' => [':love:']],
['title' => 'Thinking', 'emoji_text' => '🤔', 'emoji_shortcode' => ':thinking:', 'aliases' => [':think:', ':hmm:']],
['title' => 'Sad', 'emoji_text' => '😢', 'emoji_shortcode' => ':cry:', 'aliases' => [':sad:', ':tear:']],
['title' => 'Angry', 'emoji_text' => '😠', 'emoji_shortcode' => ':angry:', 'aliases' => [':mad:', ':rage:']],
['title' => 'Thumbs Up', 'emoji_text' => '👍', 'emoji_shortcode' => ':thumbsup:', 'aliases' => [':+1:', ':like:']],
['title' => 'Thumbs Down', 'emoji_text' => '👎', 'emoji_shortcode' => ':thumbsdown:', 'aliases' => [':-1:', ':dislike:']],
],
],
// People
[
'category' => 'People',
'emojis' => [
['title' => 'Clap', 'emoji_text' => '👏', 'emoji_shortcode' => ':clap:', 'aliases' => [':applause:']],
['title' => 'Wave', 'emoji_text' => '👋', 'emoji_shortcode' => ':wave:', 'aliases' => [':hello:', ':bye:']],
['title' => 'OK Hand', 'emoji_text' => '👌', 'emoji_shortcode' => ':ok_hand:', 'aliases' => [':ok:', ':perfect:']],
['title' => 'Victory', 'emoji_text' => '✌️', 'emoji_shortcode' => ':v:', 'aliases' => [':victory:', ':peace:']],
['title' => 'Muscle', 'emoji_text' => '💪', 'emoji_shortcode' => ':muscle:', 'aliases' => [':strong:', ':flex:']],
],
],
// Nature
[
'category' => 'Nature',
'emojis' => [
['title' => 'Sun', 'emoji_text' => '☀️', 'emoji_shortcode' => ':sunny:', 'aliases' => [':sun:']],
['title' => 'Fire', 'emoji_text' => '🔥', 'emoji_shortcode' => ':fire:', 'aliases' => [':flame:', ':hot:']],
['title' => 'Star', 'emoji_text' => '⭐', 'emoji_shortcode' => ':star:', 'aliases' => []],
['title' => 'Rainbow', 'emoji_text' => '🌈', 'emoji_shortcode' => ':rainbow:', 'aliases' => []],
['title' => 'Plant', 'emoji_text' => '🌱', 'emoji_shortcode' => ':seedling:', 'aliases' => [':plant:', ':sprout:']],
],
],
// Food
[
'category' => 'Food',
'emojis' => [
['title' => 'Pizza', 'emoji_text' => '🍕', 'emoji_shortcode' => ':pizza:', 'aliases' => []],
['title' => 'Coffee', 'emoji_text' => '☕', 'emoji_shortcode' => ':coffee:', 'aliases' => [':cafe:']],
['title' => 'Beer', 'emoji_text' => '🍺', 'emoji_shortcode' => ':beer:', 'aliases' => [':beers:']],
['title' => 'Cake', 'emoji_text' => '🎂', 'emoji_shortcode' => ':birthday:', 'aliases' => [':cake:']],
['title' => 'Apple', 'emoji_text' => '🍎', 'emoji_shortcode' => ':apple:', 'aliases' => []],
],
],
// Objects
[
'category' => 'Objects',
'emojis' => [
['title' => 'Heart', 'emoji_text' => '❤️', 'emoji_shortcode' => ':heart:', 'aliases' => [':love_heart:', ':red_heart:']],
['title' => 'Broken Heart', 'emoji_text' => '💔', 'emoji_shortcode' => ':broken_heart:', 'aliases' => []],
['title' => 'Alarm Clock', 'emoji_text' => '⏰', 'emoji_shortcode' => ':alarm_clock:', 'aliases' => [':alarm:']],
['title' => 'Check Mark', 'emoji_text' => '✅', 'emoji_shortcode' => ':white_check_mark:', 'aliases' => [':check:', ':done:']],
['title' => 'X Mark', 'emoji_text' => '❌', 'emoji_shortcode' => ':x:', 'aliases' => [':cross:', ':no:']],
],
],
];
// Insert emojis
$displayOrder = 0;
foreach ($emojis as $categoryData) {
$category = $categoryModels[$categoryData['category']];
foreach ($categoryData['emojis'] as $emojiData) {
$displayOrder++;
$emoji = Emoji::create([
'title' => $emojiData['title'],
'emoji_text' => $emojiData['emoji_text'],
'emoji_shortcode' => $emojiData['emoji_shortcode'],
'image_url' => null,
'sprite_mode' => false,
'sprite_params' => null,
'emoji_category_id' => $category->id,
'display_order' => $displayOrder,
]);
// Create aliases
foreach ($emojiData['aliases'] as $alias) {
EmojiAlias::create([
'emoji_id' => $emoji->id,
'alias' => $alias,
]);
}
}
}
}
}

View file

@ -0,0 +1,8 @@
{
"label": "API",
"position": 8,
"link": {
"type": "generated-index",
"description": "Complete API documentation for all endpoints"
}
}

View file

@ -0,0 +1,8 @@
{
"label": "Emoji",
"position": 2,
"link": {
"type": "generated-index",
"description": "API endpoints for managing emojis, categories, and aliases"
}
}

View file

@ -0,0 +1,456 @@
---
title: Emoji Aliases API
sidebar_position: 2
---
# Emoji Aliases API
The Emoji Aliases API allows you to manage alternative shortcodes for emojis, enabling multiple text triggers to map to the same emoji (e.g., `:happy:`, `:joy:`, `:lol:` all pointing to 😂).
## Base URL
```
/api/emoji/aliases
```
## Endpoints
### List Aliases
Get a paginated list of emoji aliases with optional filtering and relationship loading.
```http
GET /api/emoji/aliases
```
#### Query Parameters
| Parameter | Type | Description |
|--------------|---------|----------------------------------------|
| `emoji_id` | integer | Filter by emoji ID |
| `search` | string | Search in alias text |
| `with_emoji` | boolean | Include emoji and category information |
| `page` | integer | Page number (default: 1) |
| `per_page` | integer | Items per page (default: 50, max: 100) |
#### Example Request
```bash
curl -X GET "https://api.example.com/api/emoji/aliases?emoji_id=1&with_emoji=1" \
-H "Accept: application/json"
```
#### Example Response
```json
{
"data": [
{
"id": 1,
"alias": ":grin:",
"emoji": {
"id": 1,
"title": "Grinning Face",
"emoji_text": "😀",
"emoji_shortcode": ":grinning:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 1,
"category": {
"id": 1,
"title": "Smileys",
"display_order": 1,
"emojis_count": null,
"emojis": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"aliases": null,
"aliases_count": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
{
"id": 2,
"alias": ":happy:",
"emoji": {
"id": 1,
"title": "Grinning Face",
"emoji_text": "😀",
"emoji_shortcode": ":grinning:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 1,
"category": {
"id": 1,
"title": "Smileys",
"display_order": 1,
"emojis_count": null,
"emojis": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"aliases": null,
"aliases_count": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
],
"links": {
"first": "https://api.example.com/api/emoji/aliases?page=1",
"last": "https://api.example.com/api/emoji/aliases?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"per_page": 50,
"total": 2
}
}
```
---
### Search Aliases
Search emoji aliases using Laravel Scout for full-text search capabilities.
```http
GET /api/emoji/aliases/search
```
#### Query Parameters
| Parameter | Type | Required | Description |
|--------------|---------|----------|-------------------------------------------|
| `q` | string | Yes | Search query (minimum 1 character) |
| `limit` | integer | No | Number of results (default: 20, max: 100) |
| `with_emoji` | boolean | No | Include emoji and category information |
#### Example Request
```bash
curl -X GET "https://api.example.com/api/emoji/aliases/search?q=hap&limit=5&with_emoji=1" \
-H "Accept: application/json"
```
#### Example Response
```json
{
"data": [
{
"id": 2,
"alias": ":happy:",
"emoji": {
"id": 1,
"title": "Grinning Face",
"emoji_text": "😀",
"emoji_shortcode": ":grinning:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 1,
"category": {
"id": 1,
"title": "Smileys",
"display_order": 1,
"emojis_count": null,
"emojis": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"aliases": null,
"aliases_count": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
]
}
```
---
### Get Alias
Get a specific emoji alias by ID with full relationship data.
```http
GET /api/emoji/aliases/{id}
```
#### Path Parameters
| Parameter | Type | Description |
|-----------|---------|--------------|
| `id` | integer | The alias ID |
#### Example Request
```bash
curl -X GET "https://api.example.com/api/emoji/aliases/1" \
-H "Accept: application/json"
```
#### Example Response
```json
{
"data": {
"id": 1,
"alias": ":grin:",
"emoji": {
"id": 1,
"title": "Grinning Face",
"emoji_text": "😀",
"emoji_shortcode": ":grinning:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 1,
"category": {
"id": 1,
"title": "Smileys",
"display_order": 1,
"emojis_count": null,
"emojis": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"aliases": null,
"aliases_count": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
}
```
---
### Create Alias
Create a new alias for an existing emoji.
```http
POST /api/emoji/aliases
```
#### Request Body
| Field | Type | Required | Description |
|------------|---------|----------|------------------------------------------------------|
| `emoji_id` | integer | Yes | The emoji ID this alias points to |
| `alias` | string | Yes | The alias in `:name:` format (max 255 chars, unique) |
#### Example Request
```bash
curl -X POST "https://api.example.com/api/emoji/aliases" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"emoji_id": 1,
"alias": ":cheerful:"
}'
```
#### Example Response
```json
{
"data": {
"id": 3,
"alias": ":cheerful:",
"emoji": null,
"created_at": "2025-01-01T12:00:00Z",
"updated_at": "2025-01-01T12:00:00Z"
}
}
```
---
### Update Alias
Update an existing emoji alias.
```http
PUT /api/emoji/aliases/{id}
PATCH /api/emoji/aliases/{id}
```
#### Path Parameters
| Parameter | Type | Description |
|-----------|---------|--------------|
| `id` | integer | The alias ID |
#### Request Body
| Field | Type | Required | Description |
|------------|---------|----------|------------------------------------------------------|
| `emoji_id` | integer | No | The emoji ID this alias points to |
| `alias` | string | No | The alias in `:name:` format (max 255 chars, unique) |
#### Example Request
```bash
curl -X PATCH "https://api.example.com/api/emoji/aliases/3" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"alias": ":joyful:"
}'
```
#### Example Response
```json
{
"data": {
"id": 3,
"alias": ":joyful:",
"emoji": null,
"created_at": "2025-01-01T12:00:00Z",
"updated_at": "2025-01-01T12:30:00Z"
}
}
```
---
### Delete Alias
Delete an emoji alias. The associated emoji remains unchanged.
```http
DELETE /api/emoji/aliases/{id}
```
#### Path Parameters
| Parameter | Type | Description |
|-----------|---------|--------------|
| `id` | integer | The alias ID |
#### Example Request
```bash
curl -X DELETE "https://api.example.com/api/emoji/aliases/3" \
-H "Accept: application/json"
```
#### Example Response
```
HTTP/1.1 204 No Content
```
## Error Responses
### 404 Not Found
```json
{
"message": "No query results for model [App\\Models\\EmojiAlias] 999"
}
```
### 422 Validation Error
```json
{
"message": "The given data was invalid.",
"errors": {
"emoji_id": ["The selected emoji id is invalid."],
"alias": [
"The alias must be in the format :name: (e.g., :happy:)",
"This alias is already taken.",
"This alias conflicts with an existing emoji shortcode."
]
}
}
```
### 400 Search Validation Error
```json
{
"message": "The given data was invalid.",
"errors": {
"q": ["The q field is required."],
"limit": ["The limit must not be greater than 100."]
}
}
```
## Use Cases
Aliases are useful for creating alternative ways to access the same emoji:
### 1. Multiple Language Support
```json
{
"emoji_shortcode": ":smile:",
"aliases": [":sonrisa:", ":sourire:", ":笑顔:"]
}
```
### 2. Slack-style Flexibility
```json
{
"emoji_shortcode": ":thumbsup:",
"aliases": [":+1:", ":like:", ":approve:"]
}
```
### 3. Legacy Compatibility
```json
{
"emoji_shortcode": ":laughing:",
"aliases": [":lol:", ":rofl:", ":-D"]
}
```
### 4. Common Misspellings
```json
{
"emoji_shortcode": ":receive:",
"aliases": [":recieve:", ":recive:"]
}
```
## Validation Rules
- **alias**: Must be unique across all aliases and cannot conflict with any emoji shortcode
- **Format**: Must follow the `:name:` pattern using letters, numbers, underscores, and hyphens
- **emoji_id**: Must reference an existing emoji
- **Cross-table uniqueness**: Aliases cannot use the same text as any emoji's primary shortcode
## Notes
- Aliases are returned ordered by `alias` alphabetically
- When an emoji is deleted, all its aliases are automatically deleted (cascade)
- Search functionality uses Laravel Scout for fast full-text search
- Maximum search results per request is 100
- Aliases can be reassigned to different emojis by updating the `emoji_id`
- The same alias text cannot exist more than once in the system

View file

@ -0,0 +1,282 @@
---
title: Emoji Categories API
sidebar_position: 3
---
# Emoji Categories API
The Emoji Categories API allows you to manage emoji categories used to organize emojis in the editor interface.
## Base URL
```
/api/emoji/categories
```
## Endpoints
### List Categories
Get a list of all emoji categories.
```http
GET /api/emoji/categories
```
#### Query Parameters
| Parameter | Type | Description |
|------------------|---------|-------------------------------------------|
| `with_emojis` | boolean | Include emoji count for each category |
| `include_emojis` | boolean | Include full emoji data for each category |
#### Example Request
```bash
curl -X GET "https://api.example.com/api/emoji/categories?with_emojis=1" \
-H "Accept: application/json"
```
#### Example Response
```json
{
"data": [
{
"id": 1,
"title": "Smileys",
"display_order": 1,
"emojis_count": 15,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
{
"id": 2,
"title": "People",
"display_order": 2,
"emojis_count": 8,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
]
}
```
---
### Get Category
Get a specific emoji category by ID.
```http
GET /api/emoji/categories/{id}
```
#### Path Parameters
| Parameter | Type | Description |
|-----------|---------|-----------------|
| `id` | integer | The category ID |
#### Query Parameters
| Parameter | Type | Description |
|----------------|---------|-------------------------------------------|
| `with_aliases` | boolean | Include emoji aliases when loading emojis |
#### Example Request
```bash
curl -X GET "https://api.example.com/api/emoji/categories/1?with_aliases=1" \
-H "Accept: application/json"
```
#### Example Response
```json
{
"data": {
"id": 1,
"title": "Smileys",
"display_order": 1,
"emojis": [
{
"id": 1,
"title": "Grinning",
"emoji_text": "😀",
"emoji_shortcode": ":grinning:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 1,
"aliases": [
{
"id": 1,
"alias": ":grin:",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
],
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
],
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
}
```
---
### Create Category
Create a new emoji category.
```http
POST /api/emoji/categories
```
#### Request Body
| Field | Type | Required | Description |
|-----------------|---------|----------|-------------------------------|
| `title` | string | Yes | Category name (max 255 chars) |
| `display_order` | integer | Yes | Display order (minimum 0) |
#### Example Request
```bash
curl -X POST "https://api.example.com/api/emoji/categories" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Custom Category",
"display_order": 10
}'
```
#### Example Response
```json
{
"data": {
"id": 3,
"title": "Custom Category",
"display_order": 10,
"emojis_count": null,
"emojis": null,
"created_at": "2025-01-01T12:00:00Z",
"updated_at": "2025-01-01T12:00:00Z"
}
}
```
---
### Update Category
Update an existing emoji category.
```http
PUT /api/emoji/categories/{id}
PATCH /api/emoji/categories/{id}
```
#### Path Parameters
| Parameter | Type | Description |
|-----------|---------|-----------------|
| `id` | integer | The category ID |
#### Request Body
| Field | Type | Required | Description |
|-----------------|---------|----------|-------------------------------|
| `title` | string | No | Category name (max 255 chars) |
| `display_order` | integer | No | Display order (minimum 0) |
#### Example Request
```bash
curl -X PATCH "https://api.example.com/api/emoji/categories/3" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Updated Category Name"
}'
```
#### Example Response
```json
{
"data": {
"id": 3,
"title": "Updated Category Name",
"display_order": 10,
"emojis_count": null,
"emojis": null,
"created_at": "2025-01-01T12:00:00Z",
"updated_at": "2025-01-01T12:30:00Z"
}
}
```
---
### Delete Category
Delete an emoji category. Associated emojis will have their category_id set to null.
```http
DELETE /api/emoji/categories/{id}
```
#### Path Parameters
| Parameter | Type | Description |
|-----------|---------|-----------------|
| `id` | integer | The category ID |
#### Example Request
```bash
curl -X DELETE "https://api.example.com/api/emoji/categories/3" \
-H "Accept: application/json"
```
#### Example Response
```
HTTP/1.1 204 No Content
```
## Error Responses
### 404 Not Found
```json
{
"message": "No query results for model [App\\Models\\EmojiCategory] 999"
}
```
### 422 Validation Error
```json
{
"message": "The given data was invalid.",
"errors": {
"title": ["The title field is required."],
"display_order": ["The display order must be at least 0."]
}
}
```
## Notes
- Categories are returned ordered by `display_order` ascending
- When a category is deleted, associated emojis remain but their `emoji_category_id` is set to null
- The `emojis_count` field is only included when `with_emojis=1` parameter is used
- The `emojis` field is only included when `include_emojis=1` parameter is used

View file

@ -0,0 +1,458 @@
---
title: Emoji API
sidebar_position: 1
---
# Emoji API
The Emoji API allows you to manage individual emojis, including Unicode emojis, custom images, and CSS sprite-based emojis.
## Base URL
```
/api/emoji/emojis
```
## Endpoints
### List Emoji
Get a paginated list of emojis with optional filtering and relationship loading.
```http
GET /api/emoji/emojis
```
#### Query Parameters
| Parameter | Type | Description |
|-----------------|---------|-------------------------------------------|
| `category_id` | integer | Filter by category ID |
| `search` | string | Search in shortcode, title, or emoji text |
| `with_category` | boolean | Include category information |
| `with_aliases` | boolean | Include emoji aliases |
| `page` | integer | Page number (default: 1) |
| `per_page` | integer | Items per page (default: 50, max: 100) |
#### Example Request
```bash
curl -X GET "https://api.example.com/api/emoji/emojis?category_id=1&with_aliases=1&per_page=25" \
-H "Accept: application/json"
```
#### Example Response
```json
{
"data": [
{
"id": 1,
"title": "Grinning Face",
"emoji_text": "😀",
"emoji_shortcode": ":grinning:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 1,
"category": {
"id": 1,
"title": "Smileys",
"display_order": 1,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"aliases": [
{
"id": 1,
"alias": ":grin:",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
],
"aliases_count": 1,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
],
"links": {
"first": "https://api.example.com/api/emoji/emojis?page=1",
"last": "https://api.example.com/api/emoji/emojis?page=3",
"prev": null,
"next": "https://api.example.com/api/emoji/emojis?page=2"
},
"meta": {
"current_page": 1,
"per_page": 25,
"total": 67
}
}
```
---
### Search Emoji
Search emojis using Laravel Scout for full-text search capabilities.
```http
GET /api/emoji/emojis/search
```
#### Query Parameters
| Parameter | Type | Required | Description |
|-----------------|---------|----------|-------------------------------------------|
| `q` | string | Yes | Search query (minimum 1 character) |
| `limit` | integer | No | Number of results (default: 20, max: 100) |
| `with_category` | boolean | No | Include category information |
| `with_aliases` | boolean | No | Include emoji aliases |
#### Example Request
```bash
curl -X GET "https://api.example.com/api/emoji/emojis/search?q=smile&limit=10&with_category=1" \
-H "Accept: application/json"
```
#### Example Response
```json
{
"data": [
{
"id": 1,
"title": "Smiling Face",
"emoji_text": "😊",
"emoji_shortcode": ":smile:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 2,
"category": {
"id": 1,
"title": "Smileys",
"display_order": 1,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"aliases": null,
"aliases_count": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
]
}
```
---
### Get Emoji
Get a specific emoji by ID with full relationship data.
```http
GET /api/emoji/emojis/{id}
```
#### Path Parameters
| Parameter | Type | Description |
|-----------|---------|--------------|
| `id` | integer | The emoji ID |
#### Example Request
```bash
curl -X GET "https://api.example.com/api/emoji/emojis/1" \
-H "Accept: application/json"
```
#### Example Response
```json
{
"data": {
"id": 1,
"title": "Grinning Face",
"emoji_text": "😀",
"emoji_shortcode": ":grinning:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 1,
"category": {
"id": 1,
"title": "Smileys",
"display_order": 1,
"emojis_count": null,
"emojis": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
},
"aliases": [
{
"id": 1,
"alias": ":grin:",
"emoji": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
],
"aliases_count": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
}
```
---
### Create Emoji
Create a new emoji. Supports Unicode emojis, custom images, and CSS sprites.
```http
POST /api/emoji/emojis
```
#### Request Body
| Field | Type | Required | Description |
|------------------------|---------|-------------|---------------------------------------------------------|
| `title` | string | Yes | Human-readable name (max 255 chars) |
| `emoji_text` | string | No | Unicode emoji or text emoticon (max 10 chars) |
| `emoji_shortcode` | string | Yes | Primary shortcode in `:name:` format (unique) |
| `image_url` | string | No | Path to custom image (max 500 chars) |
| `sprite_mode` | boolean | No | Whether to use CSS sprite mode (default: false) |
| `sprite_params` | object | No | Sprite parameters (required if sprite_mode is true) |
| `sprite_params.x` | integer | Conditional | X coordinate (required if sprite_mode is true) |
| `sprite_params.y` | integer | Conditional | Y coordinate (required if sprite_mode is true) |
| `sprite_params.width` | integer | Conditional | Sprite width (required if sprite_mode is true) |
| `sprite_params.height` | integer | Conditional | Sprite height (required if sprite_mode is true) |
| `sprite_params.sheet` | string | Conditional | Sprite sheet filename (required if sprite_mode is true) |
| `emoji_category_id` | integer | No | Category ID (must exist) |
| `display_order` | integer | Yes | Display order within category (minimum 0) |
#### Example Requests
**Unicode Emoji:**
```bash
curl -X POST "https://api.example.com/api/emoji/emojis" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Heart Eyes",
"emoji_text": "😍",
"emoji_shortcode": ":heart_eyes:",
"emoji_category_id": 1,
"display_order": 5
}'
```
**Custom Image Emoji:**
```bash
curl -X POST "https://api.example.com/api/emoji/emojis" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Party Parrot",
"emoji_shortcode": ":partyparrot:",
"image_url": "/emojis/custom/partyparrot.gif",
"emoji_category_id": 2,
"display_order": 1
}'
```
**CSS Sprite Emoji:**
```bash
curl -X POST "https://api.example.com/api/emoji/emojis" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Custom Sprite",
"emoji_shortcode": ":custom_sprite:",
"sprite_mode": true,
"sprite_params": {
"x": 32,
"y": 64,
"width": 32,
"height": 32,
"sheet": "emoji-sheet-1.png"
},
"emoji_category_id": 3,
"display_order": 10
}'
```
#### Example Response
```json
{
"data": {
"id": 25,
"title": "Heart Eyes",
"emoji_text": "😍",
"emoji_shortcode": ":heart_eyes:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 5,
"category": null,
"aliases": null,
"aliases_count": null,
"created_at": "2025-01-01T12:00:00Z",
"updated_at": "2025-01-01T12:00:00Z"
}
}
```
---
### Update Emoji
Update an existing emoji.
```http
PUT /api/emoji/emojis/{id}
PATCH /api/emoji/emojis/{id}
```
#### Path Parameters
| Parameter | Type | Description |
|-----------|---------|--------------|
| `id` | integer | The emoji ID |
#### Request Body
All fields from the create endpoint are supported, but all are optional for updates.
#### Example Request
```bash
curl -X PATCH "https://api.example.com/api/emoji/emojis/25" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Love Eyes",
"display_order": 6
}'
```
#### Example Response
```json
{
"data": {
"id": 25,
"title": "Love Eyes",
"emoji_text": "😍",
"emoji_shortcode": ":heart_eyes:",
"image_url": null,
"sprite_mode": false,
"sprite_params": null,
"display_order": 6,
"category": null,
"aliases": null,
"aliases_count": null,
"created_at": "2025-01-01T12:00:00Z",
"updated_at": "2025-01-01T12:30:00Z"
}
}
```
---
### Delete Emoji
Delete an emoji. Associated aliases will be automatically deleted.
```http
DELETE /api/emoji/emojis/{id}
```
#### Path Parameters
| Parameter | Type | Description |
|-----------|---------|--------------|
| `id` | integer | The emoji ID |
#### Example Request
```bash
curl -X DELETE "https://api.example.com/api/emoji/emojis/25" \
-H "Accept: application/json"
```
#### Example Response
```
HTTP/1.1 204 No Content
```
## Error Responses
### 422 Validation Error
```json
{
"message": "The given data was invalid.",
"errors": {
"emoji_shortcode": [
"The emoji shortcode must be in the format :name: (e.g., :smile:)",
"This shortcode is already taken."
],
"sprite_params.x": [
"The sprite params.x field is required when sprite mode is true."
]
}
}
```
### 400 Search Validation Error
```json
{
"message": "The given data was invalid.",
"errors": {
"q": ["The q field is required."],
"limit": ["The limit must not be greater than 100."]
}
}
```
## Emoji Types
The API supports three types of emojis:
### 1. Unicode Emoji
- Have `emoji_text` field set to the Unicode character
- `image_url` and `sprite_params` are null
- `sprite_mode` is false
### 2. Custom Image Emoji
- Have `image_url` field set to the image path
- `emoji_text` and `sprite_params` are null
- `sprite_mode` is false
### 3. CSS Sprite Emoji
- Have `sprite_mode` set to true
- Have `sprite_params` object with position and dimensions
- `emoji_text` and `image_url` are null
## Validation Rules
- **emoji_shortcode**: Must be unique across all emojis and cannot conflict with any alias
- **Aliases**: Cannot conflict with any existing emoji shortcode
- **Format**: All shortcodes and aliases must follow the `:name:` pattern
- **Sprite params**: Required when `sprite_mode` is true
- **Category**: Must reference an existing category if provided
## Notes
- Emoji are returned ordered by `display_order` ascending
- When an emoji is deleted, all associated aliases are automatically deleted (cascade)
- Search functionality uses Laravel Scout for fast full-text search
- The `aliases_count` field is only included when aliases are not loaded
- Maximum search results per request is 100

163
docs/docs/api/overview.md Normal file
View file

@ -0,0 +1,163 @@
---
sidebar_position: 1
title: Overview
---
# API Overview
Welcome to the API documentation. This section covers all available REST API endpoints for interacting with the application programmatically.
## Base URL
All API endpoints are prefixed with `/api/` and use the following base URL:
```
https://your-domain.com/api/
```
## Authentication
The API uses Laravel Sanctum for authentication. You can authenticate using:
### 1. Session-based Authentication (Web)
For web applications using the same domain, authentication is handled via Laravel's built-in session management.
### 2. Token-based Authentication (API)
For external applications or mobile apps, use API tokens:
```bash
# Get your user token
curl -X POST https://your-domain.com/api/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "password"}'
```
Include the token in the Authorization header:
```bash
Authorization: Bearer your-token-here
```
## Response Format
All API responses follow a consistent JSON format:
### Success Response
```json
{
"data": {
"id": 1,
"title": "Example"
}
}
```
### Collection Response
```json
{
"data": [
{
"id": 1,
"title": "Example 1"
},
{
"id": 2,
"title": "Example 2"
}
],
"links": {
"first": "https://api.example.com/emoji/emojis?page=1",
"last": "https://api.example.com/emoji/emojis?page=10",
"prev": null,
"next": "https://api.example.com/emoji/emojis?page=2"
},
"meta": {
"current_page": 1,
"per_page": 50,
"total": 500
}
}
```
### Error Response
```json
{
"message": "Validation failed",
"errors": {
"title": ["The title field is required."]
}
}
```
## HTTP Status Codes
| Code | Description |
|------|--------------------------------------------|
| 200 | OK - Request successful |
| 201 | Created - Resource created successfully |
| 204 | No Content - Resource deleted successfully |
| 400 | Bad Request - Invalid request data |
| 401 | Unauthorized - Authentication required |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found - Resource not found |
| 422 | Unprocessable Entity - Validation failed |
| 500 | Internal Server Error - Server error |
## Rate Limiting
API requests are rate-limited to prevent abuse:
- **Authenticated users**: 60 requests per minute
- **Unauthenticated users**: 20 requests per minute
Rate limit headers are included in responses:
```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1640995200
```
## Pagination
List endpoints support pagination using query parameters:
- `page` - Page number (default: 1)
- `per_page` - Items per page (default: 50, max: 100)
Example:
```
GET /api/emoji/emojis?page=2&per_page=25
```
## Filtering and Searching
Many endpoints support filtering and searching:
### Query Parameters
- `search` - Text search across relevant fields
- `category_id` - Filter by category ID
- `emoji_id` - Filter by emoji ID (for aliases)
### Relationship Loading
Use these parameters to include related data:
- `with_category` - Include category information
- `with_emojis` - Include emoji list (for categories)
- `with_aliases` - Include alias list (for emojis)
- `with_emoji` - Include emoji information (for aliases)
Example:
```
GET /api/emoji/emojis?search=smile&with_category=1&with_aliases=1
```
## Versioning
The current API version is v1. Future versions will be available at:
```
/api/v2/endpoint
```
Breaking changes will always result in a new API version.

View file

@ -1,5 +1,5 @@
--- ---
sidebar_position: 2 sidebar_position: 9
title: Contributing title: Contributing
--- ---

View file

@ -1,6 +1,6 @@
{ {
"label": "Legacy System", "label": "Legacy System",
"position": 6, "position": 7,
"link": { "link": {
"type": "generated-index", "type": "generated-index",
"title": "Legacy System Documentation", "title": "Legacy System Documentation",

View file

@ -1,6 +1,6 @@
{ {
"label": "Migration Guide", "label": "Migration Guide",
"position": 5, "position": 6,
"link": { "link": {
"type": "generated-index", "type": "generated-index",
"description": "Guides for migrating from TorrentPier v2.x to the new Laravel version." "description": "Guides for migrating from TorrentPier v2.x to the new Laravel version."

View file

@ -0,0 +1,8 @@
{
"label": "Models",
"position": 5,
"link": {
"type": "generated-index",
"description": "Eloquent Models documentation for the application"
}
}

View file

@ -0,0 +1,183 @@
---
sidebar_position: 2
title: EmojiAlias
---
# EmojiAlias Model
The `EmojiAlias` model represents additional text aliases for an emoji, allowing multiple shortcodes to map to the same emoji (e.g., `:happy:`, `:joy:`, `:lol:` all mapping to 😂).
## Model Properties
### Table Name
- `emoji_aliases`
### Fillable Fields
- `emoji_id` - Foreign key to the associated emoji
- `alias` - Alternative shortcode (e.g., `:happy:`) - unique
### Timestamps
- `created_at`
- `updated_at`
## Traits
### Searchable (Laravel Scout)
The model uses Laravel Scout for alias search functionality.
```php
public function toSearchableArray()
{
return [
'id' => $this->id,
'alias' => $this->alias,
'emoji_id' => $this->emoji_id,
];
}
```
## Relationships
### Belongs To: Emoji
```php
public function emoji(): BelongsTo
{
return $this->belongsTo(Emoji::class);
}
```
## Usage Examples
### Creating Aliases
```php
use App\Models\EmojiAlias;
use App\Models\Emoji;
// Create an alias for an existing emoji
$emoji = Emoji::where('emoji_shortcode', ':joy:')->first();
$alias = EmojiAlias::create([
'emoji_id' => $emoji->id,
'alias' => ':lol:'
]);
// Create multiple aliases via the emoji relationship
$emoji->aliases()->createMany([
['alias' => ':laughing:'],
['alias' => ':rofl:'],
['alias' => ':lmao:']
]);
```
### Retrieving Aliases
```php
// Find emoji by alias
$alias = EmojiAlias::where('alias', ':lol:')->first();
$emoji = $alias->emoji;
// Get all aliases for an emoji
$emoji = Emoji::find(1);
$aliases = $emoji->aliases;
// Search for aliases
$results = EmojiAlias::search(':hap')->get();
```
### Working with Emoji Through Alias
```php
// Get the emoji details from an alias
$alias = EmojiAlias::with('emoji')->where('alias', ':lol:')->first();
echo $alias->emoji->title; // "Laughing"
echo $alias->emoji->emoji_text; // "😂"
echo $alias->emoji->emoji_shortcode; // ":joy:"
```
## Database Schema
```sql
CREATE TABLE emoji_aliases (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
emoji_id BIGINT UNSIGNED NOT NULL,
alias VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_emoji_id_alias (emoji_id, alias),
FOREIGN KEY (emoji_id) REFERENCES emojis(id) ON DELETE CASCADE
);
```
## Factory
The model includes a factory for testing:
```php
use App\Models\EmojiAlias;
use App\Models\Emoji;
// Create an alias with a new emoji
$alias = EmojiAlias::factory()->create();
// Create an alias for an existing emoji
$emoji = Emoji::factory()->create();
$alias = EmojiAlias::factory()->create([
'emoji_id' => $emoji->id,
'alias' => ':custom-alias:'
]);
// Create multiple aliases
$aliases = EmojiAlias::factory()
->count(5)
->for($emoji)
->create();
```
## Performance Considerations
- The `alias` field has a unique index for fast lookups during text replacement
- The composite index on `(emoji_id, alias)` optimizes join queries
- Cascade delete ensures aliases are removed when an emoji is deleted
- Scout integration enables fast alias searching
## Validation Considerations
When implementing the emoji management system, consider these validation rules:
1. **Uniqueness Across Tables** - An alias should not match any existing `emoji_shortcode`
2. **Format Validation** - Aliases should follow the `:name:` format
3. **Reserved Keywords** - Certain aliases might be reserved for system use
Example validation in a request class:
```php
public function rules()
{
return [
'alias' => [
'required',
'string',
'regex:/^:[a-zA-Z0-9_-]+:$/',
'unique:emoji_aliases,alias',
Rule::notIn(Emoji::pluck('emoji_shortcode')->toArray()),
],
];
}
```
## Use Cases
1. **Slack-style Flexibility** - Users can type `:+1:`, `:thumbsup:`, or `:like:` for 👍
2. **Legacy Support** - Map old emoticon codes to new emoji system
3. **Localization** - Different languages can have their own aliases
4. **User Preferences** - Users could potentially create personal aliases
## Notes
- Aliases are automatically deleted when their parent emoji is deleted (cascade)
- Each alias must be unique across the entire system
- The system should validate that an alias doesn't conflict with any emoji shortcode
- Consider implementing a maximum number of aliases per emoji for performance

View file

@ -0,0 +1,117 @@
---
sidebar_position: 3
title: EmojiCategory
---
# EmojiCategory Model
The `EmojiCategory` model represents a category for grouping emojis in the editor (e.g., "Smileys", "Animals", "Food").
## Model Properties
### Table Name
- `emoji_categories`
### Fillable Fields
- `title` - Category name (e.g., "Smileys & Emotion")
- `display_order` - Integer defining the order of the category in the editor
### Timestamps
- `created_at`
- `updated_at`
## Relationships
### Has Many: Emoji
```php
public function emojis(): HasMany
{
return $this->hasMany(Emoji::class);
}
```
Each category can contain multiple emojis.
## Usage Examples
### Creating a Category
```php
use App\Models\EmojiCategory;
$category = EmojiCategory::create([
'title' => 'Smileys & Emotion',
'display_order' => 1
]);
```
### Retrieving Categories with Emoji
```php
// Get all categories ordered by display order
$categories = EmojiCategory::orderBy('display_order')->get();
// Get category with all its emojis
$category = EmojiCategory::with('emojis')->find(1);
// Get category with emojis ordered
$category = EmojiCategory::with(['emojis' => function ($query) {
$query->orderBy('display_order');
}])->find(1);
```
### Accessing Related Emoji
```php
$category = EmojiCategory::find(1);
// Access emojis via dynamic property
foreach ($category->emojis as $emoji) {
echo $emoji->emoji_shortcode;
}
// Query emojis with additional constraints
$activeEmojis = $category->emojis()
->whereNotNull('emoji_text')
->get();
```
## Database Schema
```sql
CREATE TABLE emoji_categories (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
display_order INTEGER NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_display_order (display_order)
);
```
## Factory
The model includes a factory for testing:
```php
use App\Models\EmojiCategory;
// Create a single category
$category = EmojiCategory::factory()->create();
// Create multiple categories
$categories = EmojiCategory::factory()->count(5)->create();
// Create with specific attributes
$category = EmojiCategory::factory()->create([
'title' => 'Custom Emoji',
'display_order' => 10
]);
```
## Notes
- The `display_order` field is indexed for performance when ordering categories
- Categories can be soft-deleted if needed in future implementations
- When an emoji category is deleted, associated emojis will have their `emoji_category_id` set to null (not cascade delete)

232
docs/docs/models/emoji.md Normal file
View file

@ -0,0 +1,232 @@
---
sidebar_position: 1
title: Emoji
---
# Emoji Model
The `Emoji` model represents an individual emoji, which can be a Unicode emoji (😊), legacy text emoticon (:-)), or a custom image.
## Model Properties
### Table Name
- `emojis`
### Fillable Fields
- `title` - Human-readable name (e.g., "Smile", "Thumbs Up")
- `emoji_text` - Unicode character or text emoticon (nullable)
- `emoji_shortcode` - Primary shortcode (e.g., `:smile:`) - unique
- `image_url` - Path to custom image (nullable)
- `sprite_mode` - Boolean flag for CSS sprite usage
- `sprite_params` - JSON field for sprite parameters
- `emoji_category_id` - Foreign key to category (nullable)
- `display_order` - Integer for ordering within category
### Casts
- `sprite_mode` → boolean
- `sprite_params` → array
### Timestamps
- `created_at`
- `updated_at`
## Traits
### Searchable (Laravel Scout)
The model uses Laravel Scout for full-text search functionality.
```php
public function toSearchableArray()
{
return [
'id' => $this->id,
'emoji_shortcode' => $this->emoji_shortcode,
'emoji_text' => $this->emoji_text,
];
}
```
## Relationships
### Belongs To: Category
```php
public function category(): BelongsTo
{
return $this->belongsTo(EmojiCategory::class, 'emoji_category_id');
}
```
### Has Many: Aliases
```php
public function aliases(): HasMany
{
return $this->hasMany(EmojiAlias::class);
}
```
## Usage Examples
### Creating Emoji
```php
use App\Models\Emoji;
use App\Models\EmojiCategory;
// Create a Unicode emoji
$emoji = Emoji::create([
'title' => 'Grinning Face',
'emoji_text' => '😀',
'emoji_shortcode' => ':grinning:',
'emoji_category_id' => $category->id,
'display_order' => 1
]);
// Create a custom image emoji
$customEmoji = Emoji::create([
'title' => 'Party Parrot',
'emoji_shortcode' => ':partyparrot:',
'image_url' => '/emojis/custom/partyparrot.gif',
'emoji_category_id' => $category->id,
'display_order' => 2
]);
// Create a sprite-based emoji
$spriteEmoji = Emoji::create([
'title' => 'Custom Sprite',
'emoji_shortcode' => ':custom:',
'sprite_mode' => true,
'sprite_params' => [
'x' => 32,
'y' => 64,
'width' => 32,
'height' => 32,
'sheet' => 'emoji-sheet-1.png'
],
'emoji_category_id' => $category->id,
'display_order' => 3
]);
```
### Retrieving Emoji
```php
// Find by shortcode (remember it's unique)
$emoji = Emoji::where('emoji_shortcode', ':smile:')->first();
// Get all emojis with their aliases
$emojis = Emoji::with('aliases')->get();
// Get emojis in a specific category
$categoryEmojis = Emoji::where('emoji_category_id', $categoryId)
->orderBy('display_order')
->get();
// Get only Unicode emojis
$unicodeEmojis = Emoji::whereNotNull('emoji_text')
->whereNull('image_url')
->get();
// Get only custom image emojis
$customEmojis = Emoji::whereNull('emoji_text')
->whereNotNull('image_url')
->get();
```
### Working with Aliases
```php
$emoji = Emoji::find(1);
// Access aliases
foreach ($emoji->aliases as $alias) {
echo $alias->alias; // e.g., ":happy:", ":joy:"
}
// Add a new alias
$emoji->aliases()->create([
'alias' => ':new-alias:'
]);
```
### Search Integration
```php
// Search for emojis using Scout
$results = Emoji::search(':smile')->get();
$results = Emoji::search('😊')->get();
// Update search index
$emoji->searchable(); // Add to index
$emoji->unsearchable(); // Remove from index
```
## Database Schema
```sql
CREATE TABLE emojis (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
emoji_text VARCHAR(255) NULL,
emoji_shortcode VARCHAR(255) NOT NULL UNIQUE,
image_url VARCHAR(255) NULL,
sprite_mode BOOLEAN DEFAULT FALSE,
sprite_params JSON NULL,
emoji_category_id BIGINT UNSIGNED NULL,
display_order INTEGER NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_display_order (display_order),
INDEX idx_emoji_category_id (emoji_category_id),
FOREIGN KEY (emoji_category_id) REFERENCES emoji_categories(id) ON DELETE SET NULL
);
```
## Factory
The model includes a factory with useful states:
```php
use App\Models\Emoji;
// Create a random emoji
$emoji = Emoji::factory()->create();
// Create a custom image emoji
$customEmoji = Emoji::factory()->customImage()->create();
// Create a sprite-based emoji
$spriteEmoji = Emoji::factory()->withSprite()->create();
// Create multiple emojis with a category
$emojis = Emoji::factory()
->count(10)
->for(EmojiCategory::factory())
->create();
```
## Performance Considerations
- The `emoji_shortcode` field has a unique index for fast lookups during text replacement
- The `display_order` field is indexed for efficient ordering
- The `emoji_category_id` field is indexed for category-based queries
- Scout integration provides full-text search capabilities
## Future Enhancements
The following helper methods should be implemented in service classes:
1. **Text Replacement Helper** - Get all possible text triggers (shortcode + aliases)
2. **Render Helper** - Determine render method (unicode, image, or sprite)
3. **Eager Loading Scope** - Efficiently load emojis with aliases for replacement engine
4. **Type Check Helper** - Determine if emoji is unicode, custom image, or sprite
Example service method signatures:
```php
// EmojiService
public function getAllTriggers(Emoji $emoji): array;
public function getRenderData(Emoji $emoji): array;
public function loadForReplacement(): Collection;
public function isCustomEmoji(Emoji $emoji): bool;
```

View file

@ -81,9 +81,14 @@ const config: Config = {
}, },
items: [ items: [
{to: '/blog/welcome', label: 'Blog', position: 'left'}, {to: '/blog/welcome', label: 'Blog', position: 'left'},
{
to: '/docs/api/overview',
label: 'API',
position: 'left',
},
{ {
type: 'docSidebar', type: 'docSidebar',
sidebarId: 'tutorialSidebar', sidebarId: 'docsSidebar',
position: 'left', position: 'left',
label: 'Documentation', label: 'Documentation',
}, },

View file

@ -14,7 +14,7 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
*/ */
const sidebars: SidebarsConfig = { const sidebars: SidebarsConfig = {
// By default, Docusaurus generates a sidebar from the docs folder structure // By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], docsSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually // But you can create a sidebar manually
/* /*

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Before After
Before After

View file

@ -1,8 +1,25 @@
<?php <?php
use App\Http\Controllers\Api\EmojiAliasController;
use App\Http\Controllers\Api\EmojiCategoryController;
use App\Http\Controllers\Api\EmojiController;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get('/user', function (Request $request) { Route::get('/user', function (Request $request) {
return $request->user(); return $request->user();
})->middleware('auth:sanctum'); })->middleware('auth:sanctum');
// Emoji API Routes
Route::prefix('emoji')->group(function () {
// Emoji - search routes must come before resource routes
Route::get('emojis/search', [EmojiController::class, 'search'])->name('emojis.search');
Route::apiResource('emojis', EmojiController::class);
// Emoji Aliases - search routes must come before resource routes
Route::get('aliases/search', [EmojiAliasController::class, 'search'])->name('aliases.search');
Route::apiResource('aliases', EmojiAliasController::class);
// Emoji Categories
Route::apiResource('categories', EmojiCategoryController::class);
});