mirror of
https://github.com/torrentpier/torrentpier
synced 2025-08-14 18:48:21 -07:00
feat(emoji): implement comprehensive emoji management system (#2025)
* 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 * fix(emoji): correct comment casing for Unicode emoji in EmojiFactory Updated the comment in the EmojiFactory to use consistent casing for "Unicode" in the emoji definition method. This change improves code clarity and maintains consistency in documentation style. * refactor(emoji): rename variables for clarity in EmojiAlias and EmojiCategory controllers Updated variable names in the EmojiAliasController and EmojiCategoryController to improve code readability. Changed instances of `$emojiAlias` and `$emojiCategory` to `$alias` and `$category`, respectively, in the show, update, and destroy methods. Additionally, updated the UpdateEmojiAliasRequest to reflect the new variable naming convention. This enhances consistency and clarity across the codebase. * feat(emoji): implement search request validation for emojis and aliases Added new form request classes, SearchEmojiRequest and SearchEmojiAliasRequest, to handle validation for search queries in the Emoji and EmojiAlias controllers. Updated the search methods in both controllers to utilize these new request classes, improving code organization and validation consistency. Additionally, refactored the loading of relationships in the EmojiController for better readability. * feat(emoji): add request validation for emoji and alias indexing Introduced new form request classes, IndexEmojiRequest and IndexEmojiAliasRequest, to handle validation for indexing requests in the Emoji and EmojiAlias controllers. Updated the index methods in both controllers to utilize these new request classes, enhancing validation and code organization. This change improves the overall structure and maintainability of the emoji management system.
This commit is contained in:
parent
7f79ad5217
commit
df135a2b41
54 changed files with 4600 additions and 7 deletions
111
app/Http/Controllers/Api/EmojiAliasController.php
Normal file
111
app/Http/Controllers/Api/EmojiAliasController.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Emoji\IndexEmojiAliasRequest;
|
||||
use App\Http\Requests\Emoji\SearchEmojiAliasRequest;
|
||||
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(IndexEmojiAliasRequest $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 $alias, Request $request)
|
||||
{
|
||||
$alias->load(['emoji' => function ($query) {
|
||||
$query->with('category');
|
||||
}]);
|
||||
|
||||
return new EmojiAliasResource($alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateEmojiAliasRequest $request, EmojiAlias $alias)
|
||||
{
|
||||
$alias->update($request->validated());
|
||||
|
||||
if ($request->get('with_emoji')) {
|
||||
$alias->load(['emoji' => function ($query) {
|
||||
$query->with('category');
|
||||
}]);
|
||||
}
|
||||
|
||||
return new EmojiAliasResource($alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(EmojiAlias $alias)
|
||||
{
|
||||
$alias->delete();
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search aliases using Laravel Scout.
|
||||
*/
|
||||
public function search(SearchEmojiAliasRequest $request)
|
||||
{
|
||||
$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 $category, Request $request)
|
||||
{
|
||||
$category->load([
|
||||
'emojis' => function ($query) use ($request) {
|
||||
$query->orderBy('display_order');
|
||||
if ($request->get('with_aliases')) {
|
||||
$query->with('aliases');
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
return new EmojiCategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateEmojiCategoryRequest $request, EmojiCategory $category)
|
||||
{
|
||||
$category->update($request->validated());
|
||||
|
||||
return new EmojiCategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(EmojiCategory $category)
|
||||
{
|
||||
$category->delete();
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
114
app/Http/Controllers/Api/EmojiController.php
Normal file
114
app/Http/Controllers/Api/EmojiController.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Emoji\IndexEmojiRequest;
|
||||
use App\Http\Requests\Emoji\SearchEmojiRequest;
|
||||
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(IndexEmojiRequest $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(SearchEmojiRequest $request)
|
||||
{
|
||||
$emojis = Emoji::search($request->get('q'))
|
||||
->take($request->get('limit', 20))
|
||||
->get();
|
||||
|
||||
// Load relationships if requested
|
||||
$emojis->load(array_filter([
|
||||
$request->get('with_category') ? 'category' : null,
|
||||
$request->get('with_aliases') ? 'aliases' : null,
|
||||
]));
|
||||
|
||||
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)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
44
app/Http/Requests/Emoji/IndexEmojiAliasRequest.php
Normal file
44
app/Http/Requests/Emoji/IndexEmojiAliasRequest.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Emoji;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class IndexEmojiAliasRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'emoji_id' => 'sometimes|integer|exists:emojis,id',
|
||||
'search' => 'sometimes|string|max:255',
|
||||
'per_page' => 'sometimes|integer|min:1|max:100',
|
||||
'with_emoji' => 'sometimes|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// Convert string boolean values to actual booleans
|
||||
if ($this->has('with_emoji')) {
|
||||
$this->merge([
|
||||
'with_emoji' => filter_var($this->with_emoji, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
49
app/Http/Requests/Emoji/IndexEmojiRequest.php
Normal file
49
app/Http/Requests/Emoji/IndexEmojiRequest.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Emoji;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class IndexEmojiRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'category_id' => 'sometimes|integer|exists:emoji_categories,id',
|
||||
'search' => 'sometimes|string|max:255',
|
||||
'per_page' => 'sometimes|integer|min:1|max:100',
|
||||
'with_category' => 'sometimes|boolean',
|
||||
'with_aliases' => 'sometimes|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// Convert string boolean values to actual booleans
|
||||
$booleanFields = ['with_category', 'with_aliases'];
|
||||
|
||||
foreach ($booleanFields as $field) {
|
||||
if ($this->has($field)) {
|
||||
$this->merge([
|
||||
$field => filter_var($this->get($field), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
app/Http/Requests/Emoji/SearchEmojiAliasRequest.php
Normal file
29
app/Http/Requests/Emoji/SearchEmojiAliasRequest.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Emoji;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SearchEmojiAliasRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'q' => 'required|string|min:1',
|
||||
'limit' => 'integer|min:1|max:100',
|
||||
];
|
||||
}
|
||||
}
|
29
app/Http/Requests/Emoji/SearchEmojiRequest.php
Normal file
29
app/Http/Requests/Emoji/SearchEmojiRequest.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Emoji;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SearchEmojiRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'q' => 'required|string|min:1',
|
||||
'limit' => 'integer|min:1|max:100',
|
||||
];
|
||||
}
|
||||
}
|
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('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
|
||||
foreach ($emojis as $categoryData) {
|
||||
$category = $categoryModels[$categoryData['category']];
|
||||
$displayOrder = 0;
|
||||
|
||||
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);
|
||||
});
|
||||
|
|
347
tests/Feature/Api/EmojiAliasTest.php
Normal file
347
tests/Feature/Api/EmojiAliasTest.php
Normal file
|
@ -0,0 +1,347 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Emoji;
|
||||
use App\Models\EmojiAlias;
|
||||
use App\Models\EmojiCategory;
|
||||
|
||||
describe('Emoji Alias API Endpoints', function () {
|
||||
test('can list aliases', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
EmojiAlias::factory()->count(3)->create(['emoji_id' => $emoji->id]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/aliases');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'alias',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
],
|
||||
'links',
|
||||
'meta',
|
||||
])
|
||||
->assertJsonCount(3, 'data');
|
||||
});
|
||||
|
||||
test('can filter aliases by emoji', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji1 = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
$emoji2 = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
EmojiAlias::factory()->count(2)->create(['emoji_id' => $emoji1->id]);
|
||||
EmojiAlias::factory()->count(3)->create(['emoji_id' => $emoji2->id]);
|
||||
|
||||
$response = $this->getJson("/api/emoji/aliases?emoji_id={$emoji1->id}");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonCount(2, 'data');
|
||||
});
|
||||
|
||||
test('can search aliases', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
EmojiAlias::factory()->create([
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':happy:',
|
||||
]);
|
||||
EmojiAlias::factory()->create([
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':sad:',
|
||||
]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/aliases?search=happy');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonCount(1, 'data')
|
||||
->assertJsonPath('data.0.alias', ':happy:');
|
||||
});
|
||||
|
||||
test('can include emoji and category with aliases', function () {
|
||||
$category = EmojiCategory::factory()->create(['title' => 'Test Category']);
|
||||
$emoji = Emoji::factory()->create([
|
||||
'emoji_category_id' => $category->id,
|
||||
'title' => 'Test Emoji',
|
||||
]);
|
||||
EmojiAlias::factory()->create(['emoji_id' => $emoji->id]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/aliases?with_emoji=1');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.0.emoji.title', 'Test Emoji')
|
||||
->assertJsonPath('data.0.emoji.category.title', 'Test Category');
|
||||
});
|
||||
|
||||
test('can get specific alias', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
$alias = EmojiAlias::factory()->create([
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':test_alias:',
|
||||
]);
|
||||
|
||||
$response = $this->getJson("/api/emoji/aliases/{$alias->id}");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.alias', ':test_alias:')
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'id',
|
||||
'alias',
|
||||
'emoji',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns 404 for non-existent alias', function () {
|
||||
$response = $this->getJson('/api/emoji/aliases/999');
|
||||
|
||||
$response->assertNotFound();
|
||||
});
|
||||
|
||||
test('can create alias', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
$data = [
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':new_alias:',
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/emoji/aliases', $data);
|
||||
|
||||
$response->assertCreated()
|
||||
->assertJsonPath('data.alias', ':new_alias:')
|
||||
->assertJsonPath('data.emoji', null); // Not loaded by default
|
||||
|
||||
$this->assertDatabaseHas('emoji_aliases', [
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':new_alias:',
|
||||
]);
|
||||
});
|
||||
|
||||
test('validates required fields when creating alias', function () {
|
||||
$response = $this->postJson('/api/emoji/aliases', []);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['emoji_id', 'alias']);
|
||||
});
|
||||
|
||||
test('validates alias format', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
$response = $this->postJson('/api/emoji/aliases', [
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => 'invalid-format',
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['alias']);
|
||||
});
|
||||
|
||||
test('validates unique alias', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
EmojiAlias::factory()->create([
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':existing:',
|
||||
]);
|
||||
|
||||
$response = $this->postJson('/api/emoji/aliases', [
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':existing:',
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['alias']);
|
||||
});
|
||||
|
||||
test('validates emoji exists', function () {
|
||||
$response = $this->postJson('/api/emoji/aliases', [
|
||||
'emoji_id' => 999,
|
||||
'alias' => ':valid_alias:',
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['emoji_id']);
|
||||
});
|
||||
|
||||
test('prevents alias from conflicting with emoji shortcode', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji1 = Emoji::factory()->create([
|
||||
'emoji_category_id' => $category->id,
|
||||
'emoji_shortcode' => ':existing_emoji:',
|
||||
]);
|
||||
$emoji2 = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
$response = $this->postJson('/api/emoji/aliases', [
|
||||
'emoji_id' => $emoji2->id,
|
||||
'alias' => ':existing_emoji:',
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['alias']);
|
||||
});
|
||||
|
||||
test('can update alias', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji1 = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
$emoji2 = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
$alias = EmojiAlias::factory()->create(['emoji_id' => $emoji1->id]);
|
||||
|
||||
$data = [
|
||||
'emoji_id' => $emoji2->id,
|
||||
'alias' => ':updated_alias:',
|
||||
];
|
||||
|
||||
$response = $this->patchJson("/api/emoji/aliases/{$alias->id}", $data);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.alias', ':updated_alias:');
|
||||
|
||||
$this->assertDatabaseHas('emoji_aliases', [
|
||||
'id' => $alias->id,
|
||||
'emoji_id' => $emoji2->id,
|
||||
'alias' => ':updated_alias:',
|
||||
]);
|
||||
});
|
||||
|
||||
test('can partially update alias', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
$alias = EmojiAlias::factory()->create([
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':original:',
|
||||
]);
|
||||
|
||||
$response = $this->patchJson("/api/emoji/aliases/{$alias->id}", [
|
||||
'alias' => ':updated:',
|
||||
]);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.alias', ':updated:');
|
||||
|
||||
// emoji_id should remain unchanged
|
||||
$this->assertDatabaseHas('emoji_aliases', [
|
||||
'id' => $alias->id,
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':updated:',
|
||||
]);
|
||||
});
|
||||
|
||||
test('can delete alias', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
$alias = EmojiAlias::factory()->create(['emoji_id' => $emoji->id]);
|
||||
|
||||
$response = $this->deleteJson("/api/emoji/aliases/{$alias->id}");
|
||||
|
||||
$response->assertNoContent();
|
||||
|
||||
$this->assertDatabaseMissing('emoji_aliases', ['id' => $alias->id]);
|
||||
|
||||
// Emoji should still exist
|
||||
$this->assertDatabaseHas('emojis', ['id' => $emoji->id]);
|
||||
});
|
||||
|
||||
test('supports pagination', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
EmojiAlias::factory()->count(60)->create(['emoji_id' => $emoji->id]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/aliases?per_page=20&page=2');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('meta.current_page', 2)
|
||||
->assertJsonPath('meta.per_page', 20)
|
||||
->assertJsonCount(20, 'data');
|
||||
});
|
||||
|
||||
test('aliases are ordered alphabetically', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
EmojiAlias::factory()->create(['emoji_id' => $emoji->id, 'alias' => ':zebra:']);
|
||||
EmojiAlias::factory()->create(['emoji_id' => $emoji->id, 'alias' => ':apple:']);
|
||||
EmojiAlias::factory()->create(['emoji_id' => $emoji->id, 'alias' => ':banana:']);
|
||||
|
||||
$response = $this->getJson('/api/emoji/aliases');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.0.alias', ':apple:')
|
||||
->assertJsonPath('data.1.alias', ':banana:')
|
||||
->assertJsonPath('data.2.alias', ':zebra:');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Emoji Alias Search API', function () {
|
||||
test('can search aliases using scout', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
EmojiAlias::factory()->create([
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':happy:',
|
||||
]);
|
||||
|
||||
EmojiAlias::factory()->create([
|
||||
'emoji_id' => $emoji->id,
|
||||
'alias' => ':sad:',
|
||||
]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/aliases/search?q=happy');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'alias',
|
||||
'emoji',
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('validates search query parameter', function () {
|
||||
$response = $this->getJson('/api/emoji/aliases/search');
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['q']);
|
||||
});
|
||||
|
||||
test('validates search limit parameter', function () {
|
||||
$response = $this->getJson('/api/emoji/aliases/search?q=test&limit=150');
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['limit']);
|
||||
});
|
||||
|
||||
test('can include emoji data in search results', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
$alias = EmojiAlias::factory()->create(['emoji_id' => $emoji->id]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/aliases/search?q=test&with_emoji=1');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => [
|
||||
'emoji' => [
|
||||
'id',
|
||||
'title',
|
||||
'emoji_shortcode',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
232
tests/Feature/Api/EmojiCategoryTest.php
Normal file
232
tests/Feature/Api/EmojiCategoryTest.php
Normal file
|
@ -0,0 +1,232 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Emoji;
|
||||
use App\Models\EmojiCategory;
|
||||
|
||||
describe('Emoji Category API Endpoints', function () {
|
||||
test('can list categories', function () {
|
||||
EmojiCategory::factory()->count(3)->create();
|
||||
|
||||
$response = $this->getJson('/api/emoji/categories');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'title',
|
||||
'display_order',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
],
|
||||
])
|
||||
->assertJsonCount(3, 'data');
|
||||
});
|
||||
|
||||
test('can list categories with emoji counts', function () {
|
||||
// Ensure we start with clean slate
|
||||
EmojiCategory::query()->delete();
|
||||
Emoji::query()->delete();
|
||||
|
||||
$category1 = EmojiCategory::factory()->create([
|
||||
'title' => 'Category 1',
|
||||
'display_order' => 1,
|
||||
]);
|
||||
$category2 = EmojiCategory::factory()->create([
|
||||
'title' => 'Category 2',
|
||||
'display_order' => 2,
|
||||
]);
|
||||
|
||||
Emoji::factory()->count(5)->create(['emoji_category_id' => $category1->id]);
|
||||
Emoji::factory()->count(3)->create(['emoji_category_id' => $category2->id]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/categories?with_emojis=1');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.0.emojis_count', 5)
|
||||
->assertJsonPath('data.1.emojis_count', 3);
|
||||
});
|
||||
|
||||
test('can list categories with full emoji data', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
Emoji::factory()->count(2)->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/categories?include_emojis=1');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonCount(2, 'data.0.emojis')
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => [
|
||||
'emojis' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'title',
|
||||
'emoji_shortcode',
|
||||
'emoji_text',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('can get specific category', function () {
|
||||
$category = EmojiCategory::factory()->create([
|
||||
'title' => 'Test Category',
|
||||
'display_order' => 5,
|
||||
]);
|
||||
|
||||
$response = $this->getJson("/api/emoji/categories/{$category->id}");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.title', 'Test Category')
|
||||
->assertJsonPath('data.display_order', 5)
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'id',
|
||||
'title',
|
||||
'display_order',
|
||||
'emojis',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('can get category with emoji aliases', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
$response = $this->getJson("/api/emoji/categories/{$category->id}?with_aliases=1");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'emojis' => [
|
||||
'*' => [
|
||||
'aliases',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns 404 for non-existent category', function () {
|
||||
$response = $this->getJson('/api/emoji/categories/999');
|
||||
|
||||
$response->assertNotFound();
|
||||
});
|
||||
|
||||
test('can create category', function () {
|
||||
$data = [
|
||||
'title' => 'New Category',
|
||||
'display_order' => 10,
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/emoji/categories', $data);
|
||||
|
||||
$response->assertCreated()
|
||||
->assertJsonPath('data.title', 'New Category')
|
||||
->assertJsonPath('data.display_order', 10);
|
||||
|
||||
$this->assertDatabaseHas('emoji_categories', [
|
||||
'title' => 'New Category',
|
||||
'display_order' => 10,
|
||||
]);
|
||||
});
|
||||
|
||||
test('validates required fields when creating category', function () {
|
||||
$response = $this->postJson('/api/emoji/categories', []);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['title', 'display_order']);
|
||||
});
|
||||
|
||||
test('validates display order is non-negative', function () {
|
||||
$response = $this->postJson('/api/emoji/categories', [
|
||||
'title' => 'Test Category',
|
||||
'display_order' => -1,
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['display_order']);
|
||||
});
|
||||
|
||||
test('validates title length', function () {
|
||||
$response = $this->postJson('/api/emoji/categories', [
|
||||
'title' => str_repeat('a', 256),
|
||||
'display_order' => 1,
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['title']);
|
||||
});
|
||||
|
||||
test('can update category', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
|
||||
$data = [
|
||||
'title' => 'Updated Title',
|
||||
'display_order' => 99,
|
||||
];
|
||||
|
||||
$response = $this->patchJson("/api/emoji/categories/{$category->id}", $data);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.title', 'Updated Title')
|
||||
->assertJsonPath('data.display_order', 99);
|
||||
|
||||
$this->assertDatabaseHas('emoji_categories', [
|
||||
'id' => $category->id,
|
||||
'title' => 'Updated Title',
|
||||
'display_order' => 99,
|
||||
]);
|
||||
});
|
||||
|
||||
test('can partially update category', function () {
|
||||
$category = EmojiCategory::factory()->create([
|
||||
'title' => 'Original Title',
|
||||
'display_order' => 5,
|
||||
]);
|
||||
|
||||
$response = $this->patchJson("/api/emoji/categories/{$category->id}", [
|
||||
'title' => 'New Title',
|
||||
]);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.title', 'New Title')
|
||||
->assertJsonPath('data.display_order', 5); // Should remain unchanged
|
||||
});
|
||||
|
||||
test('can delete category', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
$response = $this->deleteJson("/api/emoji/categories/{$category->id}");
|
||||
|
||||
$response->assertNoContent();
|
||||
|
||||
$this->assertDatabaseMissing('emoji_categories', ['id' => $category->id]);
|
||||
|
||||
// Emoji should still exist but with null category_id
|
||||
$this->assertDatabaseHas('emojis', [
|
||||
'id' => $emoji->id,
|
||||
'emoji_category_id' => null,
|
||||
]);
|
||||
});
|
||||
|
||||
test('categories are ordered by display_order', function () {
|
||||
EmojiCategory::factory()->create(['title' => 'Third', 'display_order' => 3]);
|
||||
EmojiCategory::factory()->create(['title' => 'First', 'display_order' => 1]);
|
||||
EmojiCategory::factory()->create(['title' => 'Second', 'display_order' => 2]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/categories');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.0.title', 'First')
|
||||
->assertJsonPath('data.1.title', 'Second')
|
||||
->assertJsonPath('data.2.title', 'Third');
|
||||
});
|
||||
});
|
333
tests/Feature/Api/EmojiTest.php
Normal file
333
tests/Feature/Api/EmojiTest.php
Normal file
|
@ -0,0 +1,333 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Emoji;
|
||||
use App\Models\EmojiAlias;
|
||||
use App\Models\EmojiCategory;
|
||||
|
||||
describe('Emoji API Endpoints', function () {
|
||||
test('can list emojis', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emojis = Emoji::factory()->count(3)->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/emojis');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'title',
|
||||
'emoji_text',
|
||||
'emoji_shortcode',
|
||||
'image_url',
|
||||
'sprite_mode',
|
||||
'sprite_params',
|
||||
'display_order',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
],
|
||||
'links',
|
||||
'meta',
|
||||
])
|
||||
->assertJsonCount(3, 'data');
|
||||
});
|
||||
|
||||
test('can filter emojis by category', function () {
|
||||
$category1 = EmojiCategory::factory()->create();
|
||||
$category2 = EmojiCategory::factory()->create();
|
||||
|
||||
Emoji::factory()->count(2)->create(['emoji_category_id' => $category1->id]);
|
||||
Emoji::factory()->count(3)->create(['emoji_category_id' => $category2->id]);
|
||||
|
||||
$response = $this->getJson("/api/emoji/emojis?category_id={$category1->id}");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonCount(2, 'data');
|
||||
});
|
||||
|
||||
test('can search emojis', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
Emoji::factory()->create([
|
||||
'title' => 'Happy Face',
|
||||
'emoji_shortcode' => ':happy:',
|
||||
'emoji_category_id' => $category->id,
|
||||
]);
|
||||
Emoji::factory()->create([
|
||||
'title' => 'Sad Face',
|
||||
'emoji_shortcode' => ':sad:',
|
||||
'emoji_category_id' => $category->id,
|
||||
]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/emojis?search=happy');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonCount(1, 'data')
|
||||
->assertJsonPath('data.0.title', 'Happy Face');
|
||||
});
|
||||
|
||||
test('can include category and aliases with emojis', function () {
|
||||
$category = EmojiCategory::factory()->create(['title' => 'Test Category']);
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
EmojiAlias::factory()->count(2)->create(['emoji_id' => $emoji->id]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/emojis?with_category=1&with_aliases=1');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.0.category.title', 'Test Category')
|
||||
->assertJsonCount(2, 'data.0.aliases');
|
||||
});
|
||||
|
||||
test('can get specific emoji', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create([
|
||||
'title' => 'Test Emoji',
|
||||
'emoji_shortcode' => ':test:',
|
||||
'emoji_category_id' => $category->id,
|
||||
]);
|
||||
|
||||
$response = $this->getJson("/api/emoji/emojis/{$emoji->id}");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.title', 'Test Emoji')
|
||||
->assertJsonPath('data.emoji_shortcode', ':test:')
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'id',
|
||||
'title',
|
||||
'emoji_shortcode',
|
||||
'category',
|
||||
'aliases',
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns 404 for non-existent emoji', function () {
|
||||
$response = $this->getJson('/api/emoji/emojis/999');
|
||||
|
||||
$response->assertNotFound();
|
||||
});
|
||||
|
||||
test('can create unicode emoji', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
|
||||
$data = [
|
||||
'title' => 'Happy Face',
|
||||
'emoji_text' => '😊',
|
||||
'emoji_shortcode' => ':happy_face:',
|
||||
'emoji_category_id' => $category->id,
|
||||
'display_order' => 1,
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/emoji/emojis', $data);
|
||||
|
||||
$response->assertCreated()
|
||||
->assertJsonPath('data.title', 'Happy Face')
|
||||
->assertJsonPath('data.emoji_text', '😊')
|
||||
->assertJsonPath('data.emoji_shortcode', ':happy_face:');
|
||||
|
||||
$this->assertDatabaseHas('emojis', [
|
||||
'title' => 'Happy Face',
|
||||
'emoji_text' => '😊',
|
||||
'emoji_shortcode' => ':happy_face:',
|
||||
]);
|
||||
});
|
||||
|
||||
test('can create custom image emoji', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
|
||||
$data = [
|
||||
'title' => 'Custom Emoji',
|
||||
'emoji_shortcode' => ':custom:',
|
||||
'image_url' => '/emojis/custom/custom.png',
|
||||
'emoji_category_id' => $category->id,
|
||||
'display_order' => 1,
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/emoji/emojis', $data);
|
||||
|
||||
$response->assertCreated()
|
||||
->assertJsonPath('data.title', 'Custom Emoji')
|
||||
->assertJsonPath('data.image_url', '/emojis/custom/custom.png')
|
||||
->assertJsonPath('data.emoji_text', null);
|
||||
});
|
||||
|
||||
test('can create sprite emoji', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
|
||||
$data = [
|
||||
'title' => 'Sprite Emoji',
|
||||
'emoji_shortcode' => ':sprite:',
|
||||
'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' => 1,
|
||||
];
|
||||
|
||||
$response = $this->postJson('/api/emoji/emojis', $data);
|
||||
|
||||
$response->assertCreated()
|
||||
->assertJsonPath('data.sprite_mode', true)
|
||||
->assertJsonPath('data.sprite_params.x', 32)
|
||||
->assertJsonPath('data.sprite_params.sheet', 'emoji-sheet-1.png');
|
||||
});
|
||||
|
||||
test('validates required fields when creating emoji', function () {
|
||||
$response = $this->postJson('/api/emoji/emojis', []);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['title', 'emoji_shortcode', 'display_order']);
|
||||
});
|
||||
|
||||
test('validates emoji shortcode format', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
|
||||
$response = $this->postJson('/api/emoji/emojis', [
|
||||
'title' => 'Test',
|
||||
'emoji_shortcode' => 'invalid-format',
|
||||
'emoji_category_id' => $category->id,
|
||||
'display_order' => 1,
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['emoji_shortcode']);
|
||||
});
|
||||
|
||||
test('validates unique emoji shortcode', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
Emoji::factory()->create(['emoji_shortcode' => ':existing:']);
|
||||
|
||||
$response = $this->postJson('/api/emoji/emojis', [
|
||||
'title' => 'Test',
|
||||
'emoji_shortcode' => ':existing:',
|
||||
'emoji_category_id' => $category->id,
|
||||
'display_order' => 1,
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['emoji_shortcode']);
|
||||
});
|
||||
|
||||
test('validates sprite params when sprite mode is enabled', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
|
||||
$response = $this->postJson('/api/emoji/emojis', [
|
||||
'title' => 'Test',
|
||||
'emoji_shortcode' => ':test:',
|
||||
'sprite_mode' => true,
|
||||
'emoji_category_id' => $category->id,
|
||||
'display_order' => 1,
|
||||
]);
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors([
|
||||
'sprite_params.x',
|
||||
'sprite_params.y',
|
||||
'sprite_params.width',
|
||||
'sprite_params.height',
|
||||
'sprite_params.sheet',
|
||||
]);
|
||||
});
|
||||
|
||||
test('can update emoji', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
$data = [
|
||||
'title' => 'Updated Title',
|
||||
'display_order' => 99,
|
||||
];
|
||||
|
||||
$response = $this->patchJson("/api/emoji/emojis/{$emoji->id}", $data);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data.title', 'Updated Title')
|
||||
->assertJsonPath('data.display_order', 99);
|
||||
|
||||
$this->assertDatabaseHas('emojis', [
|
||||
'id' => $emoji->id,
|
||||
'title' => 'Updated Title',
|
||||
'display_order' => 99,
|
||||
]);
|
||||
});
|
||||
|
||||
test('can delete emoji', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
$emoji = Emoji::factory()->create(['emoji_category_id' => $category->id]);
|
||||
$alias = EmojiAlias::factory()->create(['emoji_id' => $emoji->id]);
|
||||
|
||||
$response = $this->deleteJson("/api/emoji/emojis/{$emoji->id}");
|
||||
|
||||
$response->assertNoContent();
|
||||
|
||||
$this->assertDatabaseMissing('emojis', ['id' => $emoji->id]);
|
||||
$this->assertDatabaseMissing('emoji_aliases', ['id' => $alias->id]);
|
||||
});
|
||||
|
||||
test('supports pagination', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
Emoji::factory()->count(60)->create(['emoji_category_id' => $category->id]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/emojis?per_page=20&page=2');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('meta.current_page', 2)
|
||||
->assertJsonPath('meta.per_page', 20)
|
||||
->assertJsonCount(20, 'data');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Emoji Search API', function () {
|
||||
test('can search emojis using scout', function () {
|
||||
$category = EmojiCategory::factory()->create();
|
||||
|
||||
// Create emojis with different content
|
||||
Emoji::factory()->create([
|
||||
'title' => 'Happy Face',
|
||||
'emoji_shortcode' => ':happy:',
|
||||
'emoji_text' => '😊',
|
||||
'emoji_category_id' => $category->id,
|
||||
]);
|
||||
|
||||
Emoji::factory()->create([
|
||||
'title' => 'Sad Face',
|
||||
'emoji_shortcode' => ':sad:',
|
||||
'emoji_text' => '😢',
|
||||
'emoji_category_id' => $category->id,
|
||||
]);
|
||||
|
||||
$response = $this->getJson('/api/emoji/emojis/search?q=happy');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'title',
|
||||
'emoji_shortcode',
|
||||
'emoji_text',
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('validates search query parameter', function () {
|
||||
$response = $this->getJson('/api/emoji/emojis/search');
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['q']);
|
||||
});
|
||||
|
||||
test('validates search limit parameter', function () {
|
||||
$response = $this->getJson('/api/emoji/emojis/search?q=test&limit=150');
|
||||
|
||||
$response->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['limit']);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue