validate([ 'per_page' => 'sometimes|integer|min:1|max:100', 'search' => 'sometimes|string|nullable|max:50', ]); $perPage = $validated['per_page'] ?? 15; $search = trim($validated['search'] ?? ''); $sortField = $request->input('sort_field', 'DBKey'); $sortOrder = strtolower($request->input('sort_order', 'asc')) === 'desc' ? 'desc' : 'asc'; // whitelist для полей сортировки и выборки $allowedSortFields = ['DBKey', 'Name', 'Level', 'AccountName']; if (!in_array($sortField, $allowedSortFields, true)) { $sortField = 'DBKey'; } // 2. Запрос с выборкой только нужных полей $query = CharBase::query()->select($allowedSortFields); if ($search !== '') { $query->where(function ($q) use ($search) { $q->where('Name', 'like', "%{$search}%") ->orWhere('AccountName', 'like', "%{$search}%"); if (ctype_digit($search)) { $q->orWhere('DBKey', (int) $search); } }); } // 3. Сортировка и пагинация $charactersPaginator = $query->orderBy($sortField, $sortOrder)->paginate($perPage); // 4. Ответ // Если по запросу не найдено ни одного персонажа, возвращаем специальный ответ // для сохранения обратной совместимости API. if ($charactersPaginator->total() === 0) { return response()->json([ 'characters' => (object) [], 'code' => -2, 'msg' => 'Characters not found.' ], 200); } // Laravel автоматически преобразует пагинатор в корректный JSON, // включая только выбранные через select() поля. return response()->json([ 'code' => 0, 'msg' => 'Characters successfully received.', 'characters' => $charactersPaginator, ], 200); } /** * Получение всех персонажей пользователя по имени пользователя. * * @param string $username * @return \Illuminate\Http\JsonResponse */ public function GetUserCharacters($username) { // Получаем всех персонажей по $username $characters = CharBase::where('AccountName', $username)->get(); // Проверяем, найдены ли персонажи if ($characters->isEmpty()) { return response()->json(['characters' => [], 'code' => -2, 'msg' => 'Characters not found for this user.'], 200); } // Подсчитываем общее время в игре для всех персонажей пользователя $totalPlayTime = $characters->sum('TotalPlayTime'); // Возвращаем только DBKey, Name, Level, GuildDBKey, TotalPlayTime персонажей $characters = $characters->map(function ($character) { return [ 'DBKey' => $character->DBKey, 'Name' => $character->Name, 'Level' => $character->Level, 'GuildDBKey' => $character->GuildDBKey, 'CharPlayTime' => $character->TotalPlayTime ]; }); return response()->json([ 'code' => 0, 'msg' => 'Characters successfully received.', 'characters' => $characters, 'totalPlayTime' => $totalPlayTime ], 200); } /** * Отправка предмета на персонажа. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function SendItemToCharacter(Request $request) { $character = CharBase::where('DBKey', $request->Owner)->first(); if (!$character) { return response()->json(['code' => -1, 'msg' => 'Character not found.'], 404); } $validator = \Validator::make($request->all(), [ 'Owner' => 'required', 'Kind' => 'required', 'RecId' => 'required', 'Amount' => 'required|numeric', 'Period' => 'required|numeric', 'evPType' => 'required', 'Comment' => 'required' ], [], [ 'Owner' => '', 'Kind' => '', 'RecId' => '', 'Amount' => '', 'Period' => '', 'evPType' => '', 'Comment' => '' ]); if (!$validator->passes()) return response()->json(['code' => -1, 'msg' => $validator->errors()], 400); CharCashItem_OutputBox::SENDITEMOUTPUTBOX($request); return response()->json([ 'code' => 0, 'msg' => 'Items successfully sent.' ], 200); } /** * Получение всех данных персонажа из указанной таблицы. * * @param int $char_id * @param string $table_name * @return \Illuminate\Http\JsonResponse */ public function GetCharacterData($char_id, $table_name) { // Получаем конфигурацию таблицы через приватный метод $config = $this->resolveTable($table_name); if (!$config) { return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400); } $modelClass = $config['model']; $key = $config['key']; // Проверяем существование персонажа if (!CharBase::find($char_id)) { return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404); } // Получаем данные $data = $modelClass::where($key, $char_id)->get(); if ($data->isEmpty()) { return response()->json(['code' => -3, 'msg' => 'No data found for this character in the specified table.'], 200); } return response()->json([ 'code' => 0, 'msg' => 'Data successfully received.', 'data' => $data ], 200); } /** * Обновление данных персонажа в указанной таблице. * * @param Request $request * @param int $char_id * @param string $table_name * @return \Illuminate\Http\JsonResponse */ public function UpdateCharacterData(Request $request, $char_id, $table_name) { // 1. Получаем конфигурацию таблицы через приватный метод $config = $this->resolveTable($table_name); if (!$config) { return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400); } $modelClass = $config['model']; $ownerKey = $config['key']; // Ключ, по которому связывается с CharBase - id персонажа $primaryKey = $config['pk']; // Для CharBase - DBKey // 2. Определяем, какую запись обновляем if ($table_name === 'CharBase') { $model = $modelClass::find($char_id); } else { $recordId = $request->input($primaryKey); if (!$recordId) { return response()->json(['code' => -4, 'msg' => "Primary key '$primaryKey' is required for this table."], 400); } $model = $modelClass::find($recordId); } if (!$model) { return response()->json(['code' => -2, 'msg' => 'Record not found.'], 404); } // Проверяем принадлежность записи персонажу if ($model->{$ownerKey} != $char_id) { return response()->json(['code' => -5, 'msg' => 'Record does not belong to the character.'], 403); } // 3. Подготавливаем данные к обновлению – защищаем PK и владение $data = $request->except([$primaryKey, $ownerKey, 'DBKey', 'Owner', 'Account']); // 4. Обновляем модель и возвращаем ответ return $this->_updateAndRespond($model, $data); } /** * Получает конкретный предмет по $char_item_id из указанной таблицы $table предметов. * * @param string $table * @param int $char_item_id * @return \Illuminate\Http\JsonResponse */ public function GetCharItem(string $table, int $char_item_id) { // Получаем конфигурацию таблицы (модель и первичный ключ) $config = $this->resolveTable($table); if (!$config) { return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400); } $modelClass = $config['model']; $pk = $config['pk']; // Ищем предмет по первичному ключу из конфигурации $item = $modelClass::where($pk, $char_item_id)->first(); if (!$item) { return response()->json([ 'code' => -2, 'msg' => 'Item not found.', ], 404); } return response()->json([ 'code' => 0, 'msg' => 'Item successfully received.', 'data' => $item, ], 200); } /** * Обновляет конкретный предмет по $char_item_id в указанной таблице $table предметов. * * @param \Illuminate\Http\Request $request * @param string $table * @param int $char_item_id * @return \Illuminate\Http\JsonResponse */ public function UpdateCharItem(Request $request, string $table, int $char_item_id) { // Получаем конфигурацию таблицы (модель и первичный ключ) $config = $this->resolveTable($table); if (!$config) { return response()->json(['code' => -1, 'msg' => 'Table not allowed or does not exist.'], 400); } $modelClass = $config['model']; $pk = $config['pk']; // Ищем предмет по первичному ключу из конфигурации $item = $modelClass::where($pk, $char_item_id)->first(); if (!$item) { return response()->json([ 'code' => -2, 'msg' => 'Item not found.', ], 404); } // Определяем защищённые поля динамически, чтобы не дать изменить PK и владельца $protected = [$pk, 'Owner', 'Account']; $data = $request->except($protected); return $this->_updateAndRespond($item, $data, 'Item successfully updated.'); } /** * Получение квеста персонажа по char_id и RecId. * Поддерживается выбор таблицы (CharQuest или CharQuest_History) через опциональный параметр $table. * * @param string $char_id * @param string $rec_id * @param string|null $table * @return \Illuminate\Http\JsonResponse */ public function GetCharQuest(string $char_id, string $rec_id, ?string $table = null) { // По умолчанию используем CharQuest $table = $table ?? 'CharQuest'; // Проверяем допустимость таблицы для данной операции $allowedQuestTables = ['CharQuest', 'CharQuest_History']; if (!in_array($table, $allowedQuestTables, true)) { return response()->json(['code' => -1, 'msg' => 'Table not allowed for this operation.'], 400); } // Проверяем существование персонажа if (!CharBase::find($char_id)) { return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404); } // Получаем конфиг модели по таблице и делаем выборку по Owner и RecId $config = $this->resolveTable($table); $modelClass = $config['model']; $quest = $modelClass::where('Owner', $char_id) ->where('RecId', $rec_id) ->first(); if (!$quest) { return response()->json([ 'code' => -3, 'msg' => 'Quest not found for this character.', ], 404); } return response()->json([ 'code' => 0, 'msg' => 'Quest successfully received.', 'data' => $quest, ], 200); } /** * Обновление квеста персонажа по char_id и RecId. * Поддерживается выбор таблицы (CharQuest или CharQuest_History) через опциональный параметр $table. * * @param \Illuminate\Http\Request $request * @param string $char_id * @param string $rec_id * @param string|null $table * @return \Illuminate\Http\JsonResponse */ public function UpdateCharQuest(Request $request, string $char_id, string $rec_id, ?string $table = null) { // По умолчанию используем CharQuest $table = $table ?? 'CharQuest'; // Проверяем допустимость таблицы для данной операции $allowedQuestTables = ['CharQuest', 'CharQuest_History']; if (!in_array($table, $allowedQuestTables, true)) { return response()->json(['code' => -1, 'msg' => 'Table not allowed for this operation.'], 400); } // Проверяем существование персонажа if (!CharBase::find($char_id)) { return response()->json(['code' => -2, 'msg' => 'Character not found.'], 404); } // Получаем конфиг модели по таблице и делаем выборку по Owner и RecId $config = $this->resolveTable($table); $modelClass = $config['model']; $quest = $modelClass::where('Owner', $char_id) ->where('RecId', $rec_id) ->first(); if (!$quest) { return response()->json([ 'code' => -3, 'msg' => 'Quest not found for this character.', ], 404); } // Защищаем поля Owner и RecId от изменения $protected = ['Owner', 'RecId']; $data = $request->except($protected); return $this->_updateAndRespond($quest, $data, 'Quest successfully updated.'); } //------------------------------ ПРИВАТНЫЕ МЕТОДЫ ----------------------------------------- /** * Возвращает конфигурацию таблицы или null, если таблица не разрешена. * * @param string $table * @return array|null */ private function resolveTable(string $table): ?array { return self::TABLES[$table] ?? null; } /** * Белый список доступных для обновления таблиц. * Используется в resolveTable(), GetCharacterData(), UpdateCharacterData(). */ private const TABLES = [ 'CharBase' => ['key' => 'DBKey', 'model' => CharBase::class, 'pk' => 'DBKey'], 'CharItem' => ['key' => 'Owner', 'model' => CharItem::class, 'pk' => 'CharItemID'], 'CharFellow' => ['key' => 'Owner', 'model' => CharFellow::class, 'pk' => 'FellowID'], 'CharCashItem_OutputBox' => ['key' => 'Owner', 'model' => CharCashItem_OutputBox::class, 'pk' => 'ItemDBIndex'], 'CharQuest' => ['key' => 'Owner', 'model' => CharQuest::class, 'pk' => 'RecId'], 'CharQuest_History' => ['key' => 'Owner', 'model' => CharQuest_History::class, 'pk' => 'RecId'], ]; /** * Общий метод для обновления модели и формирования ответа. * * @param \Illuminate\Database\Eloquent\Model $model * @param array $data * @param string $successMsg * @return \Illuminate\Http\JsonResponse */ private function _updateAndRespond(\Illuminate\Database\Eloquent\Model $model, array $data, string $successMsg = 'Data successfully updated.') { // 1. Фильтруем поля для массового присвоения $fillable = $model->getFillable(); $guarded = $model->getGuarded(); if (!empty($fillable)) { // Если есть fillable - используем только их $filteredData = array_intersect_key($data, array_flip($fillable)); } elseif ($guarded === ['*']) { // Если guarded = ['*'] - запрещено массовое присвоение return response()->json(['code' => -6, 'msg' => 'Mass assignment is not allowed for this model.'], 400); } else { // Если есть guarded (но не ['*']) - исключаем только guarded поля $filteredData = array_diff_key($data, array_flip($guarded)); } // 2. Конвертируем null-поля в '', чтобы вместо NULL в БД сохранялась пустая строка $filteredData = array_map(static fn($v) => $v === null ? '' : $v, $filteredData); if (empty($filteredData)) { return response()->json(['code' => -6, 'msg' => 'No valid fields to update.'], 400); } // 3. Пытаемся обновить запись try { $ok = $model->update($filteredData); } catch (\Throwable $e) { Log::error('Database error while updating record:', ['error' => $e->getMessage()]); return response()->json([ 'code' => -7, 'msg' => 'Database error while updating record.', 'error' => $e->getMessage(), ], 500); } if (!$ok) { return response()->json([ 'code' => -8, 'msg' => 'Update failed, record not modified.', ], 500); } // 4. Возвращаем успешный ответ return response()->json([ 'code' => 0, 'msg' => $successMsg, 'data' => $model->fresh() ], 200); } }