mirror of
https://github.com/torrentpier/torrentpier
synced 2025-08-21 22:03:49 -07:00
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:
parent
7f79ad5217
commit
c8656d7576
47 changed files with 3545 additions and 7 deletions
114
app/Http/Controllers/Api/EmojiAliasController.php
Normal file
114
app/Http/Controllers/Api/EmojiAliasController.php
Normal 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);
|
||||
}
|
||||
}
|
80
app/Http/Controllers/Api/EmojiCategoryController.php
Normal file
80
app/Http/Controllers/Api/EmojiCategoryController.php
Normal 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);
|
||||
}
|
||||
}
|
119
app/Http/Controllers/Api/EmojiController.php
Normal file
119
app/Http/Controllers/Api/EmojiController.php
Normal 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);
|
||||
}
|
||||
}
|
67
app/Http/Controllers/Emoji/EmojiAliasController.php
Normal file
67
app/Http/Controllers/Emoji/EmojiAliasController.php
Normal 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)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
67
app/Http/Controllers/Emoji/EmojiCategoryController.php
Normal file
67
app/Http/Controllers/Emoji/EmojiCategoryController.php
Normal 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)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
67
app/Http/Controllers/Emoji/EmojiController.php
Normal file
67
app/Http/Controllers/Emoji/EmojiController.php
Normal 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)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
49
app/Http/Requests/Emoji/StoreEmojiAliasRequest.php
Normal file
49
app/Http/Requests/Emoji/StoreEmojiAliasRequest.php
Normal 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.',
|
||||
];
|
||||
}
|
||||
}
|
29
app/Http/Requests/Emoji/StoreEmojiCategoryRequest.php
Normal file
29
app/Http/Requests/Emoji/StoreEmojiCategoryRequest.php
Normal 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',
|
||||
];
|
||||
}
|
||||
}
|
60
app/Http/Requests/Emoji/StoreEmojiRequest.php
Normal file
60
app/Http/Requests/Emoji/StoreEmojiRequest.php
Normal 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.',
|
||||
];
|
||||
}
|
||||
}
|
51
app/Http/Requests/Emoji/UpdateEmojiAliasRequest.php
Normal file
51
app/Http/Requests/Emoji/UpdateEmojiAliasRequest.php
Normal 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.',
|
||||
];
|
||||
}
|
||||
}
|
29
app/Http/Requests/Emoji/UpdateEmojiCategoryRequest.php
Normal file
29
app/Http/Requests/Emoji/UpdateEmojiCategoryRequest.php
Normal 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',
|
||||
];
|
||||
}
|
||||
}
|
62
app/Http/Requests/Emoji/UpdateEmojiRequest.php
Normal file
62
app/Http/Requests/Emoji/UpdateEmojiRequest.php
Normal 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.',
|
||||
];
|
||||
}
|
||||
}
|
25
app/Http/Resources/EmojiAliasResource.php
Normal file
25
app/Http/Resources/EmojiAliasResource.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
27
app/Http/Resources/EmojiCategoryResource.php
Normal file
27
app/Http/Resources/EmojiCategoryResource.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
33
app/Http/Resources/EmojiResource.php
Normal file
33
app/Http/Resources/EmojiResource.php
Normal 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
83
app/Models/Emoji.php
Normal 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
47
app/Models/EmojiAlias.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
32
app/Models/EmojiCategory.php
Normal file
32
app/Models/EmojiCategory.php
Normal 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);
|
||||
}
|
||||
}
|
65
app/Policies/EmojiAliasPolicy.php
Normal file
65
app/Policies/EmojiAliasPolicy.php
Normal 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;
|
||||
}
|
||||
}
|
65
app/Policies/EmojiCategoryPolicy.php
Normal file
65
app/Policies/EmojiCategoryPolicy.php
Normal 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;
|
||||
}
|
||||
}
|
65
app/Policies/EmojiPolicy.php
Normal file
65
app/Policies/EmojiPolicy.php
Normal 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;
|
||||
}
|
||||
}
|
25
database/factories/EmojiAliasFactory.php
Normal file
25
database/factories/EmojiAliasFactory.php
Normal 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() . ':',
|
||||
];
|
||||
}
|
||||
}
|
24
database/factories/EmojiCategoryFactory.php
Normal file
24
database/factories/EmojiCategoryFactory.php
Normal 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),
|
||||
];
|
||||
}
|
||||
}
|
64
database/factories/EmojiFactory.php
Normal file
64
database/factories/EmojiFactory.php
Normal 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',
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
};
|
|
@ -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');
|
||||
}
|
||||
};
|
|
@ -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');
|
||||
}
|
||||
};
|
16
database/seeders/EmojiAliasSeeder.php
Normal file
16
database/seeders/EmojiAliasSeeder.php
Normal 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
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
16
database/seeders/EmojiCategorySeeder.php
Normal file
16
database/seeders/EmojiCategorySeeder.php
Normal 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
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
124
database/seeders/EmojiSeeder.php
Normal file
124
database/seeders/EmojiSeeder.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
docs/docs/api/_category_.json
Normal file
8
docs/docs/api/_category_.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"label": "API",
|
||||
"position": 8,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Complete API documentation for all endpoints"
|
||||
}
|
||||
}
|
8
docs/docs/api/emojis/_category_.json
Normal file
8
docs/docs/api/emojis/_category_.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"label": "Emoji",
|
||||
"position": 2,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "API endpoints for managing emojis, categories, and aliases"
|
||||
}
|
||||
}
|
456
docs/docs/api/emojis/aliases.md
Normal file
456
docs/docs/api/emojis/aliases.md
Normal 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
|
282
docs/docs/api/emojis/categories.md
Normal file
282
docs/docs/api/emojis/categories.md
Normal 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
|
458
docs/docs/api/emojis/emojis.md
Normal file
458
docs/docs/api/emojis/emojis.md
Normal 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
163
docs/docs/api/overview.md
Normal 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.
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
sidebar_position: 9
|
||||
title: Contributing
|
||||
---
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"label": "Legacy System",
|
||||
"position": 6,
|
||||
"position": 7,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"title": "Legacy System Documentation",
|
||||
"description": "Documentation and analysis of the legacy TorrentPier v2.x system that is being modernized with Laravel.",
|
||||
"keywords": ["legacy", "torrentpier", "migration"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"label": "Migration Guide",
|
||||
"position": 5,
|
||||
"position": 6,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Guides for migrating from TorrentPier v2.x to the new Laravel version."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
8
docs/docs/models/_category_.json
Normal file
8
docs/docs/models/_category_.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"label": "Models",
|
||||
"position": 5,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Eloquent Models documentation for the application"
|
||||
}
|
||||
}
|
183
docs/docs/models/emoji-alias.md
Normal file
183
docs/docs/models/emoji-alias.md
Normal 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
|
117
docs/docs/models/emoji-category.md
Normal file
117
docs/docs/models/emoji-category.md
Normal 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
232
docs/docs/models/emoji.md
Normal 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;
|
||||
```
|
|
@ -81,9 +81,14 @@ const config: Config = {
|
|||
},
|
||||
items: [
|
||||
{to: '/blog/welcome', label: 'Blog', position: 'left'},
|
||||
{
|
||||
to: '/docs/api/overview',
|
||||
label: 'API',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'tutorialSidebar',
|
||||
sidebarId: 'docsSidebar',
|
||||
position: 'left',
|
||||
label: 'Documentation',
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
|||
*/
|
||||
const sidebars: SidebarsConfig = {
|
||||
// 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
|
||||
/*
|
||||
|
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
@ -1,8 +1,25 @@
|
|||
<?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\Support\Facades\Route;
|
||||
|
||||
Route::get('/user', function (Request $request) {
|
||||
return $request->user();
|
||||
})->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);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue