This commit is contained in:
dimon8
2026-01-07 19:29:49 +00:00
parent 31b79d70c5
commit d8a8759504
14 changed files with 363 additions and 78 deletions

View File

@@ -0,0 +1,74 @@
<?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',
'custom_field' => 'required|string|min:2'
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
'custom_field' => $validated['custom_field'],
]);
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

@@ -9,9 +9,15 @@ use Illuminate\Http\Response;
class ComponentsController extends Controller class ComponentsController extends Controller
{ {
public function index(){ public function index()
return response()->json(Component::all()->toJson()); {
} $components = Component::with('user', 'componentType')
->where('is_official', true)
->orWhere('created_by_user_id', auth()->id())
->get();
return response()->json($components);
}
@@ -26,49 +32,73 @@ class ComponentsController extends Controller
return response()->json($component); return response()->json($component);
} }
public function create(Request $request) public function store(Request $request)
{ {
$name = $request->get(key:'name'); $validated = $request->validate([
$type = $request->get(key:'type'); 'name' => 'required|string|max:255',
$brand = $request->get(key:'brand'); 'price' => 'required|numeric|min:0',
$model = $request->get(key:'model'); 'component_type_id' => 'required|exists:component_types,id',
$price = $request->get(key:'price'); 'specifications' => 'nullable|array',
]);
$component = new Component(); $component = Component::create([
$component->name = $name; 'name' => $validated['name'],
$component->type = $type; 'price' => $validated['price'],
$component->brand = $brand; 'component_type_id' => $validated['component_type_id'],
$component->model = $model; 'specifications' => $validated['specifications'] ?? null,
$component->price = $price; 'is_official' => false, // всегда false для пользователя
'created_by_user_id' => auth()->id(), // автоматически привязываем к пользователю
]);
$component->save(); 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($component->toJson()); return response()->json([
'message' => 'Вы не можете редактировать этот компонент.'
], 403);
} }
public function update(Request $request, int $id): JsonResponse{ $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([ return response()->json([
'name' => $request->get('name'), 'message' => 'Вы не можете удалить этот компонент.'
'type' => $request->get('type'), ], 403);
'brand' => $request->get('brand'),
'model' => $request->get('model'),
'price' => $request->get('price'),
], Response::HTTP_ACCEPTED);
}
public function destroy(int $id): JsonResponse
{
// мы бы здесь написали вызов запроса delete из БД
return response()->json([
'success' => true,
], Response::HTTP_ACCEPTED);
} }
$component->delete();
return response()->json([
'message' => 'Компонент удалён.'
]);
}

View File

@@ -33,8 +33,5 @@ class UsersController extends Controller
return ['token' => $user->createToken('frontend')->plainTextToken]; return ['token' => $user->createToken('frontend')->plainTextToken];
} }
} }

42
app/Models/AiTask.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class AiTask extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'name',
'prompt_template',
'is_active',
];
protected $casts = [
'is_active' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
// Связь с пользователем (если шаблон пользовательский)
public function user()
{
return $this->belongsTo(User::class);
}
// Общие (глобальные) шаблоны — где user_id IS NULL
public function scopeGlobal($query)
{
return $query->whereNull('user_id');
}
// Активные шаблоны
public function scopeActive($query)
{
return $query->where('is_active', true);
}
}

View File

@@ -11,29 +11,24 @@ class Component extends Model
protected $fillable = [ protected $fillable = [
'name', 'name',
'component_type_id',
'price', 'price',
'component_type_id',
'specifications', 'specifications',
'is_official', 'is_official',
'created_by_user_id', 'created_by_user_id',
]; ];
protected $casts = [ protected $casts = [
'specifications' => 'array', // Автоматически преобразует JSON в массив 'specifications' => 'array', // автоматически преобразует JSON в массив
'is_official' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
]; ];
// Связь с типом компонента public function user()
public function type()
{
return $this->belongsTo(ComponentType::class, 'component_type_id');
}
// Связь с пользователем (если добавил пользователь)
public function createdBy()
{ {
return $this->belongsTo(User::class, 'created_by_user_id'); return $this->belongsTo(User::class, 'created_by_user_id');
} }
public function componentType()
{
return $this->belongsTo(ComponentType::class, 'component_type_id');
}
} }

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PcBuildComponent extends Model
{
use HasFactory;
protected $fillable = [
'pc_build_id',
'component_id',
'quantity',
];
protected $casts = [
'quantity' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
// Связь с сборкой
public function build()
{
return $this->belongsTo(PcBuild::class);
}
// Связь с компонентом
public function component()
{
return $this->belongsTo(Component::class);
}
}

View File

@@ -24,6 +24,7 @@ class User extends Authenticatable
'name', 'name',
'email', 'email',
'password', 'password',
'custom_field'
]; ];
/** /**

View File

@@ -8,7 +8,7 @@
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.2",
"laravel/tinker": "^2.10.1" "laravel/tinker": "^2.10.1"
}, },
"require-dev": { "require-dev": {

19
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d3c16cb86c42230c6c023d9a5d9bcf42", "content-hash": "8f387a0734f3bf879214e4aa2fca6e2f",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@@ -1333,16 +1333,16 @@
}, },
{ {
"name": "laravel/sanctum", "name": "laravel/sanctum",
"version": "v4.2.0", "version": "v4.2.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/sanctum.git", "url": "https://github.com/laravel/sanctum.git",
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677" "reference": "fd447754d2d3f56950d53b930128af2e3b617de9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677", "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd447754d2d3f56950d53b930128af2e3b617de9",
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677", "reference": "fd447754d2d3f56950d53b930128af2e3b617de9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1356,9 +1356,8 @@
}, },
"require-dev": { "require-dev": {
"mockery/mockery": "^1.6", "mockery/mockery": "^1.6",
"orchestra/testbench": "^9.0|^10.0", "orchestra/testbench": "^9.15|^10.8",
"phpstan/phpstan": "^1.10", "phpstan/phpstan": "^1.10"
"phpunit/phpunit": "^11.3"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@@ -1393,7 +1392,7 @@
"issues": "https://github.com/laravel/sanctum/issues", "issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum" "source": "https://github.com/laravel/sanctum"
}, },
"time": "2025-07-09T19:45:24+00:00" "time": "2026-01-06T23:11:51+00:00"
}, },
{ {
"name": "laravel/serializable-closure", "name": "laravel/serializable-closure",
@@ -8463,5 +8462,5 @@
"php": "^8.2" "php": "^8.2"
}, },
"platform-dev": {}, "platform-dev": {},
"plugin-api-version": "2.6.0" "plugin-api-version": "2.9.0"
} }

View File

@@ -11,16 +11,19 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('components', function (Blueprint $table) { Schema::create('components', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name'); // Например: "Intel Core i5-12400F" $table->string('name');
$table->foreignId('component_type_id')->constrained()->onDelete('cascade'); $table->decimal('price', 10, 2);
$table->decimal('price', 10, 2); $table->unsignedBigInteger('component_type_id'); // ссылка на тип компонента
$table->json('specifications')->nullable(); // Для хранения характеристик $table->json('specifications')->nullable(); // JSON-поле для характеристик
$table->boolean('is_official')->default(true); // true = админ, false = пользователь $table->boolean('is_official')->default(false); // официальный или нет
$table->foreignId('created_by_user_id')->nullable()->constrained('users')->onDelete('set null'); $table->unsignedBigInteger('created_by_user_id')->nullable(); // кто создал
$table->timestamps(); $table->timestamps();
});
$table->foreign('component_type_id')->references('id')->on('component_types');
$table->foreign('created_by_user_id')->references('id')->on('users')->onDelete('set null');
});
} }

View File

@@ -12,7 +12,7 @@ return new class extends Migration
public function up(): void public function up(): void
{ {
Schema::table('users', function (Blueprint $table) { Schema::table('users', function (Blueprint $table) {
$table->text('custom_field'); $table->text('custom_field')->nullable()->default(null);
// //
}); });
} }

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePcBuildComponentsTable extends Migration
{
public function up()
{
Schema::create('pc_build_components', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('pc_build_id');
$table->unsignedBigInteger('component_id');
$table->timestamps();
// Внешние ключи
$table->foreign('pc_build_id')->references('id')->on('pc_builds')->onDelete('cascade');
$table->foreign('component_id')->references('id')->on('components')->onDelete('cascade');
// Уникальность пары (build + component), если нужно
$table->unique(['pc_build_id', 'component_id']);
});
}
public function down()
{
Schema::dropIfExists('pc_build_components');
}
}

View File

@@ -0,0 +1,61 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
{
Schema::create('ai_tasks', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
$table->string('name');
$table->text('prompt_template');
$table->boolean('is_active')->default(true);
$table->timestamps();
// Индекс для поиска активных шаблонов
$table->index(['is_active']);
});
// Заполняем базовыми шаблонами от админа (user_id = NULL)
DB::table('ai_tasks')->insert([
[
'name' => 'Игровой ПК до 50 000 ₽',
'prompt_template' => 'Собери бюджетный игровой ПК до 50000 рублей. Цель: игры на средних настройках в 1080p.',
'is_active' => true,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'Игровой ПК до 100 000 ₽',
'prompt_template' => 'Собери мощный игровой ПК до 100000 рублей. Цель: игры на ультра в 1440p, 60+ FPS.',
'is_active' => true,
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'Офисный ПК',
'prompt_template' => 'Собери надёжный ПК для офиса и учёбы. Бюджет до 40000 рублей. Важна тишина и энергоэффективность.',
'is_active' => true,
'created_at' => now(),
'updated_at' => now(),
],
]);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ai_tasks');
}
};

View File

@@ -4,21 +4,39 @@ use App\Http\Controllers\UsersController;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ComponentsController; use App\Http\Controllers\ComponentsController;
use App\Http\Controllers\AuthController;
Route::get('/users', function (Request $request) { Route::get('/users', function (Request $request) {
return $request->user(); return $request->user();
})->middleware('auth:sanctum'); })->middleware('auth:sanctum');
Route::get('components', [ComponentsController::class, 'index']);
Route::get('components/{id}', [ComponentsController::class, 'show']); Route::get('components/{id}', [ComponentsController::class, 'show']);
Route::post('components', [ComponentsController::class, 'create']);
Route::post('users', [UsersController::class, 'create']); Route::post('users', [UsersController::class, 'create']);
Route::put('/components', [ComponentsController::class, 'update']);
Route::delete('/components', [ComponentsController::class, 'destroy']);
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
// Защита маршрутов — только для авторизованных
Route::middleware('auth:sanctum')->group(function () {
Route::post('/logout', [AuthController::class, 'logout']);
});
Route::middleware('auth:sanctum')->group(function () {
Route::get('/components', [ComponentsController::class, 'index']);
Route::post('/components', [ComponentsController::class, 'store']);
Route::put('/components/{id}', [ComponentsController::class, 'update']);
Route::delete('/components/{id}', [ComponentsController::class, 'destroy']);
});