Начальный коммит: рабочая версия с исправленной авторизацией

This commit is contained in:
root
2026-01-11 19:15:02 +00:00
commit 2d98209ce1
206 changed files with 20957 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers;
use App\Services\AiSuggestorService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class AiController extends Controller
{
protected $aiService;
// ✅ Используем DI — Laravel сам передаст сервис
public function __construct(AiSuggestorService $aiService)
{
$this->aiService = $aiService;
}
public function suggest(Request $request)
{
$validated = $request->validate([
'task_id' => 'nullable|string',
'custom_prompt' => 'nullable|string',
'budget' => 'nullable|numeric|min:0',
]);
try {
// ✅ Вызываем через DI
$result = $this->aiService->suggest(
$request->input('task_id'),
$request->input('custom_prompt'),
$request->input('budget')
);
// ✅ Возвращаем ТОЛЬКО ID — быстро и безопасно
return response()->json([
'message' => 'Сборка успешно сгенерирована ИИ.',
'build_id' => $result['build']->id,
], 201);
} catch (\Exception $e) {
Log::error('Ошибка в AI-сервисе', [
'user_id' => auth()->id(),
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return response()->json([
'message' => 'Не удалось сгенерировать сборку.',
'error' => $e->getMessage()
], 500);
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
/**
* Регистрация пользователя.
*/
public function register(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
<<<<<<< HEAD
=======
'custom_field' => 'required|string|min:2'
>>>>>>> origin/main
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
<<<<<<< HEAD
'custom_field' => $request->custom_field ?? 'user', // ← ключевая строка
=======
'custom_field' => $validated['custom_field'],
>>>>>>> origin/main
]);
return response()->json([
'message' => 'Пользователь зарегистрирован.',
'user' => $user,
'token' => $user->createToken('auth_token')->plainTextToken
], 201);
}
/**
* Вход пользователя.
*/
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['Неверные учётные данные.'],
]);
}
return response()->json([
'message' => 'Успешный вход.',
'user' => $user,
'token' => $user->createToken('auth_token')->plainTextToken
]);
}
/**
* Выход (инвалидация токена).
*/
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json([
'message' => 'Вы успешно вышли из системы.'
]);
}
}

View File

@@ -0,0 +1,213 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Models\Component;
use Illuminate\Http\Response;
class ComponentsController extends Controller
{
<<<<<<< HEAD
public function index(Request $request)
{
// Начинаем с базового запроса
$query = Component::with('componentType'); // ← важно! Загружаем связь
// Фильтр по пользователю и статусу
$query->where(function ($q) {
$q->where('is_official', true)
->orWhere('created_by_user_id', auth()->id());
});
// Если передан параметр type — фильтруем по code
if ($request->has('type')) {
$typeCode = $request->input('type');
// Убедимся, что такой тип существует
$type = \App\Models\ComponentType::where('code', $typeCode)->first();
if (!$type) {
return response()->json([], 200); // пустой массив, если тип не найден
}
// Фильтруем компоненты по component_type_id
$query->where('component_type_id', $type->id);
}
$components = $query->get();
// Добавляем поле "type" для удобства фронта
$components = $components->map(function ($comp) {
$comp->type = $comp->componentType?->code ?? 'unknown';
return $comp;
});
return response()->json($components);
}
=======
public function index()
{
$components = Component::with('user', 'componentType')
->where('is_official', true)
->orWhere('created_by_user_id', auth()->id())
->get();
return response()->json($components);
}
>>>>>>> origin/main
public function show($id)
{
$component = Component::find($id);
if (!$component) {
return response()->json(['message' => 'Component not found'], 404);
}
return response()->json($component);
}
<<<<<<< HEAD
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'price' => 'required|numeric|min:0',
'component_type_id' => 'required|exists:component_types,id',
'specifications' => 'nullable|array',
]);
$component = Component::create([
'name' => $validated['name'],
'price' => $validated['price'],
'component_type_id' => $validated['component_type_id'],
'specifications' => $validated['specifications'] ?? null,
'is_official' => false,
'created_by_user_id' => auth()->id(),
]);
return response()->json([
'message' => 'Компонент успешно создан.',
'component' => $component
], 201);
}
public function update(Request $request, $id)
{
$component = Component::findOrFail($id);
if ($component->created_by_user_id !== auth()->id() || $component->is_official) {
return response()->json([
'message' => 'Вы не можете редактировать этот компонент.'
], 403);
}
$validated = $request->validate([
'name' => 'required|string|max:255',
'price' => 'required|numeric|min:0',
'component_type_id' => 'required|exists:component_types,id',
'specifications' => 'nullable|array',
]);
$component->update($validated);
return response()->json([
'message' => 'Компонент обновлён.',
'component' => $component
]);
}
public function destroy($id)
{
$component = Component::findOrFail($id);
if ($component->created_by_user_id !== auth()->id() || $component->is_official) {
return response()->json([
'message' => 'Вы не можете удалить этот компонент.'
], 403);
}
$component->delete();
return response()->json([
'message' => 'Компонент удалён.'
]);
}
}
=======
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'price' => 'required|numeric|min:0',
'component_type_id' => 'required|exists:component_types,id',
'specifications' => 'nullable|array',
]);
$component = Component::create([
'name' => $validated['name'],
'price' => $validated['price'],
'component_type_id' => $validated['component_type_id'],
'specifications' => $validated['specifications'] ?? null,
'is_official' => false, // всегда false для пользователя
'created_by_user_id' => auth()->id(), // автоматически привязываем к пользователю
]);
return response()->json([
'message' => 'Компонент успешно создан.',
'component' => $component
], 201);
}
public function update(Request $request, $id)
{
$component = Component::findOrFail($id);
// Проверяем, что компонент принадлежит пользователю и не официальный
if ($component->created_by_user_id !== auth()->id() || $component->is_official) {
return response()->json([
'message' => 'Вы не можете редактировать этот компонент.'
], 403);
}
$validated = $request->validate([
'name' => 'required|string|max:255',
'price' => 'required|numeric|min:0',
'component_type_id' => 'required|exists:component_types,id',
'specifications' => 'nullable|array',
]);
$component->update($validated);
return response()->json([
'message' => 'Компонент обновлён.',
'component' => $component
]);
}
public function destroy($id)
{
$component = Component::findOrFail($id);
// Проверяем, что компонент принадлежит пользователю и не официальный
if ($component->created_by_user_id !== auth()->id() || $component->is_official) {
return response()->json([
'message' => 'Вы не можете удалить этот компонент.'
], 403);
}
$component->delete();
return response()->json([
'message' => 'Компонент удалён.'
]);
}
}
>>>>>>> origin/main

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@@ -0,0 +1,246 @@
<?php
namespace App\Http\Controllers;
use App\Models\PCBuild;
use App\Models\Component;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Services\BuildValidator;
class PCBuildsController extends Controller
{
/**
* @OA\Get(
* path="/api/builds",
* summary="Получить список своих сборок",
* tags={"PC Builds"},
* @OA\Response(response=200, description="Список сборок"),
* @OA\Response(response=401, description="Неавторизован")
* )
*/
public function index()
{
$builds = PCBuild::where('user_id', auth()->id())
->with('components')
->get();
return response()->json($builds);
}
/**
* @OA\Post(
* path="/api/builds",
* summary="Создать новую сборку",
* tags={"PC Builds"},
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"name", "component_ids"},
* @OA\Property(property="name", type="string", example="Игровой ПК 2025"),
* @OA\Property(property="description", type="string", nullable=true, example="Для игр в 1440p"),
* @OA\Property(property="component_ids", type="array", @OA\Items(type="integer", example=1)),
* @OA\Property(property="is_ai_generated", type="boolean", default=false),
* @OA\Property(property="ai_prompt", type="string", nullable=true, example="Сборка до 1000$ для игр")
* )
* ),
* @OA\Response(response=201, description="Сборка создана"),
* @OA\Response(response=400, description="Ошибка валидации"),
* @OA\Response(response=403, description="Запрещено: чужие компоненты")
* )
*/
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'component_ids' => 'required|array|min:1',
'component_ids.*' => 'exists:components,id',
'is_ai_generated' => 'nullable|boolean',
'ai_prompt' => 'nullable|string',
]);
// Проверяем: все компоненты — либо официальные, либо ваши
$invalidComponents = Component::whereIn('id', $validated['component_ids'])
->where(function ($query) {
$query->where('is_official', false)
->where('created_by_user_id', '!=', auth()->id());
})
->pluck('name', 'id')
->toArray();
if (!empty($invalidComponents)) {
return response()->json([
'message' => 'Запрещено использовать неофициальные компоненты, созданные другими пользователями.',
'invalid_components' => $invalidComponents
], 403);
}
// 👇 ВСТАВЛЯЕМ ПРОВЕРКУ СОВМЕСТИМОСТИ ЗДЕСЬ — ПЕРЕД СОЗДАНИЕМ СБОРКИ
$validator = new BuildValidator();
$compatibility = $validator->validateCompatibility($validated['component_ids']);
if (!$compatibility['valid']) {
return response()->json([
'message' => 'Сборка содержит несовместимые компоненты.',
'errors' => $compatibility['errors'],
'warnings' => $compatibility['warnings']
], 422); // 422 Unprocessable Entity
}
// ✅ Только если совместимость OK — создаём сборку
$build = PCBuild::create([
'user_id' => auth()->id(),
'name' => $validated['name'],
'description' => $validated['description'] ?? null,
'is_ai_generated' => $validated['is_ai_generated'] ?? false,
'ai_prompt' => $validated['ai_prompt'] ?? null,
]);
$build->components()->attach($validated['component_ids']);
return response()->json([
'message' => 'Сборка успешно создана.',
'build' => $build->load('components'),
'compatibility' => $compatibility // опционально — для отладки
], 201);
}
/**
* @OA\Get(
* path="/api/builds/{id}",
* summary="Получить одну сборку по ID",
* tags={"PC Builds"},
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
* @OA\Response(response=200, description="Сборка найдена"),
* @OA\Response(response=403, description="Запрещено: не ваша сборка"),
* @OA\Response(response=404, description="Не найдено")
* )
*/
public function show($id)
{
$build = PCBuild::with('components')->findOrFail($id);
if ($build->user_id !== auth()->id()) {
return response()->json([
'message' => 'Вы не можете просматривать эту сборку.'
], 403);
}
return response()->json($build);
}
/**
* @OA\Put(
* path="/api/builds/{id}",
* summary="Обновить сборку",
* tags={"PC Builds"},
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"name", "component_ids"},
* @OA\Property(property="name", type="string"),
* @OA\Property(property="description", type="string", nullable=true),
* @OA\Property(property="component_ids", type="array", @OA\Items(type="integer")),
* @OA\Property(property="is_ai_generated", type="boolean"),
* @OA\Property(property="ai_prompt", type="string", nullable=true)
* )
* ),
* @OA\Response(response=200, description="Сборка обновлена"),
* @OA\Response(response=403, description="Запрещено")
* )
*/
public function update(Request $request, $id)
{
$build = PCBuild::findOrFail($id);
if ($build->user_id !== auth()->id()) {
return response()->json([
'message' => 'Вы не можете редактировать эту сборку.'
], 403);
}
$validated = $request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'component_ids' => 'required|array|min:1',
'component_ids.*' => 'exists:components,id',
'is_ai_generated' => 'nullable|boolean',
'ai_prompt' => 'nullable|string',
]);
// Проверка: все компоненты — либо официальные, либо ваши
$invalidComponents = Component::whereIn('id', $validated['component_ids'])
->where(function ($query) {
$query->where('is_official', false)
->where('created_by_user_id', '!=', auth()->id());
})
->pluck('name', 'id')
->toArray();
if (!empty($invalidComponents)) {
return response()->json([
'message' => 'Запрещено использовать неофициальные компоненты, созданные другими пользователями.',
'invalid_components' => $invalidComponents
], 403);
}
// 👇 ВСТАВЛЯЕМ ПРОВЕРКУ СОВМЕСТИМОСТИ ЗДЕСЬ — ПЕРЕД ОБНОВЛЕНИЕМ
$validator = new BuildValidator();
$compatibility = $validator->validateCompatibility($validated['component_ids']);
if (!$compatibility['valid']) {
return response()->json([
'message' => 'Сборка содержит несовместимые компоненты.',
'errors' => $compatibility['errors'],
'warnings' => $compatibility['warnings']
], 422);
}
// ✅ Обновляем сборку
DB::transaction(function () use ($build, $validated) {
$build->update([
'name' => $validated['name'],
'description' => $validated['description'] ?? null,
'is_ai_generated' => $validated['is_ai_generated'] ?? $build->is_ai_generated,
'ai_prompt' => $validated['ai_prompt'] ?? $build->ai_prompt,
]);
$build->components()->sync($validated['component_ids']);
});
return response()->json([
'message' => 'Сборка обновлена.',
'build' => $build->load('components'),
'compatibility' => $compatibility // опционально
]);
}
/**
* @OA\Delete(
* path="/api/builds/{id}",
* summary="Удалить сборку",
* tags={"PC Builds"},
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
* @OA\Response(response=200, description="Сборка удалена"),
* @OA\Response(response=403, description="Запрещено")
* )
*/
public function destroy($id)
{
$build = PCBuild::findOrFail($id);
if ($build->user_id !== auth()->id()) {
return response()->json([
'message' => 'Вы не можете удалить эту сборку.'
], 403);
}
$build->delete();
return response()->json([
'message' => 'Сборка удалена.'
]);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use App\Mail\RegisterUserMail;
use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use App\Models\User;
use Illuminate\Http\Request;
class UsersController extends Controller
{
public function create(Request $request){
$user = new User();
$name = $request->get('name');
$email = $request->get('email');
$password = Hash::make($request->get('password'));
$user->name = $name;
$user->email = $email;
$user->password = $password;
$user->save();
$adminEmail = 'dimon.cozlow2017@yandex.ru';
dispatch(function () use ($user, $adminEmail) {
Mail::to($adminEmail)->send(new RegisterUserMail($user->name));
});
return ['token' => $user->createToken('frontend')->plainTextToken];
}
}