Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8a8759504 |
74
app/Http/Controllers/AuthController.php
Normal file
74
app/Http/Controllers/AuthController.php
Normal 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' => 'Вы успешно вышли из системы.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,14 @@ use Illuminate\Http\Response;
|
||||
|
||||
class ComponentsController extends Controller
|
||||
{
|
||||
public function index(){
|
||||
return response()->json(Component::all()->toJson());
|
||||
public function index()
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
public function store(Request $request)
|
||||
{
|
||||
$name = $request->get(key:'name');
|
||||
$type = $request->get(key:'type');
|
||||
$brand = $request->get(key:'brand');
|
||||
$model = $request->get(key:'model');
|
||||
$price = $request->get(key:'price');
|
||||
$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 = new Component();
|
||||
$component->name = $name;
|
||||
$component->type = $type;
|
||||
$component->brand = $brand;
|
||||
$component->model = $model;
|
||||
$component->price = $price;
|
||||
|
||||
$component->save();
|
||||
|
||||
|
||||
|
||||
|
||||
return response()->json($component->toJson());
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id): JsonResponse{
|
||||
$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([
|
||||
'name' => $request->get('name'),
|
||||
'type' => $request->get('type'),
|
||||
'brand' => $request->get('brand'),
|
||||
'model' => $request->get('model'),
|
||||
'price' => $request->get('price'),
|
||||
], Response::HTTP_ACCEPTED);
|
||||
|
||||
'message' => 'Компонент успешно создан.',
|
||||
'component' => $component
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function destroy(int $id): JsonResponse
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
// мы бы здесь написали вызов запроса delete из БД
|
||||
$component = Component::findOrFail($id);
|
||||
|
||||
// Проверяем, что компонент принадлежит пользователю и не официальный
|
||||
if ($component->created_by_user_id !== auth()->id() || $component->is_official) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
], Response::HTTP_ACCEPTED);
|
||||
'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' => 'Компонент удалён.'
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -33,8 +33,5 @@ class UsersController extends Controller
|
||||
return ['token' => $user->createToken('frontend')->plainTextToken];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
42
app/Models/AiTask.php
Normal file
42
app/Models/AiTask.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -11,29 +11,24 @@ class Component extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'component_type_id',
|
||||
'price',
|
||||
'component_type_id',
|
||||
'specifications',
|
||||
'is_official',
|
||||
'created_by_user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'specifications' => 'array', // Автоматически преобразует JSON в массив
|
||||
'is_official' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'specifications' => 'array', // автоматически преобразует JSON в массив
|
||||
];
|
||||
|
||||
// Связь с типом компонента
|
||||
public function type()
|
||||
{
|
||||
return $this->belongsTo(ComponentType::class, 'component_type_id');
|
||||
}
|
||||
|
||||
// Связь с пользователем (если добавил пользователь)
|
||||
public function createdBy()
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function componentType()
|
||||
{
|
||||
return $this->belongsTo(ComponentType::class, 'component_type_id');
|
||||
}
|
||||
}
|
||||
35
app/Models/PCBuildComponent.php
Normal file
35
app/Models/PCBuildComponent.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ class User extends Authenticatable
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'custom_field'
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/sanctum": "^4.2",
|
||||
"laravel/tinker": "^2.10.1"
|
||||
},
|
||||
"require-dev": {
|
||||
|
||||
19
composer.lock
generated
19
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "d3c16cb86c42230c6c023d9a5d9bcf42",
|
||||
"content-hash": "8f387a0734f3bf879214e4aa2fca6e2f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -1333,16 +1333,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/sanctum",
|
||||
"version": "v4.2.0",
|
||||
"version": "v4.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sanctum.git",
|
||||
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677"
|
||||
"reference": "fd447754d2d3f56950d53b930128af2e3b617de9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
|
||||
"reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677",
|
||||
"url": "https://api.github.com/repos/laravel/sanctum/zipball/fd447754d2d3f56950d53b930128af2e3b617de9",
|
||||
"reference": "fd447754d2d3f56950d53b930128af2e3b617de9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1356,9 +1356,8 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6",
|
||||
"orchestra/testbench": "^9.0|^10.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^11.3"
|
||||
"orchestra/testbench": "^9.15|^10.8",
|
||||
"phpstan/phpstan": "^1.10"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -1393,7 +1392,7 @@
|
||||
"issues": "https://github.com/laravel/sanctum/issues",
|
||||
"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",
|
||||
@@ -8463,5 +8462,5 @@
|
||||
"php": "^8.2"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -13,13 +13,16 @@ return new class extends Migration
|
||||
{
|
||||
Schema::create('components', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name'); // Например: "Intel Core i5-12400F"
|
||||
$table->foreignId('component_type_id')->constrained()->onDelete('cascade');
|
||||
$table->string('name');
|
||||
$table->decimal('price', 10, 2);
|
||||
$table->json('specifications')->nullable(); // Для хранения характеристик
|
||||
$table->boolean('is_official')->default(true); // true = админ, false = пользователь
|
||||
$table->foreignId('created_by_user_id')->nullable()->constrained('users')->onDelete('set null');
|
||||
$table->unsignedBigInteger('component_type_id'); // ссылка на тип компонента
|
||||
$table->json('specifications')->nullable(); // JSON-поле для характеристик
|
||||
$table->boolean('is_official')->default(false); // официальный или нет
|
||||
$table->unsignedBigInteger('created_by_user_id')->nullable(); // кто создал
|
||||
$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');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ return new class extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->text('custom_field');
|
||||
$table->text('custom_field')->nullable()->default(null);
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -4,21 +4,39 @@ use App\Http\Controllers\UsersController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\ComponentsController;
|
||||
use App\Http\Controllers\AuthController;
|
||||
|
||||
Route::get('/users', function (Request $request) {
|
||||
return $request->user();
|
||||
})->middleware('auth:sanctum');
|
||||
|
||||
Route::get('components', [ComponentsController::class, 'index']);
|
||||
|
||||
Route::get('components/{id}', [ComponentsController::class, 'show']);
|
||||
|
||||
|
||||
Route::post('components', [ComponentsController::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']);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user