PNG IHDR x sBIT|d pHYs + tEXtSoftware www.inkscape.org< ,tEXtComment
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Coin;
use App\Services\CryptoService;
use App\Services\ExchangeSupportChecker;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
class CoinController extends Controller
{
protected CryptoService $cryptoService;
protected ExchangeSupportChecker $supportChecker;
public function __construct(CryptoService $cryptoService, ExchangeSupportChecker $supportChecker)
{
$this->cryptoService = $cryptoService;
$this->supportChecker = $supportChecker;
}
/**
* Display a listing of the coins
*/
public function index(Request $request): View
{
$query = Coin::query();
// Apply search filter
if ($request->filled('search')) {
$search = $request->get('search');
$query->where(function($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('symbol', 'like', "%{$search}%")
->orWhere('display_symbol', 'like', "%{$search}%");
});
}
// Apply status filter
if ($request->filled('status')) {
$status = $request->get('status');
if ($status === 'active') {
$query->where('is_active', true);
} elseif ($status === 'inactive') {
$query->where('is_active', false);
}
}
// Apply support status filter
if ($request->filled('support_status')) {
$query->where('price_check_status', $request->get('support_status'));
}
$coins = $query->ordered()->paginate(20);
return view('admin.coins.index', compact('coins'));
}
/**
* Show the form for creating a new coin
*/
public function create(): View
{
return view('admin.coins.create');
}
/**
* Store a newly created coin
*/
public function store(Request $request): RedirectResponse
{
$validated = $this->validateCoin($request);
try {
// Handle image upload/URL
$validated['image'] = $this->handleImageUpload($request);
$coin = Coin::create($validated);
// Check exchange support if requested
if ($request->boolean('check_support')) {
$this->supportChecker->updateCoinSupportStatus($coin);
}
return redirect()
->route('admin.coins.index')
->with('success', "Coin '{$coin->name}' created successfully!");
} catch (\Exception $e) {
Log::error('Error creating coin', [
'error' => $e->getMessage(),
'data' => $validated
]);
return back()
->withInput()
->with('error', 'Failed to create coin: ' . $e->getMessage());
}
}
/**
* Display the specified coin
*/
public function show(Coin $coin): View
{
// Get support status if available
$supportStatus = null;
if ($coin->supported_exchanges) {
$supportResults = $coin->supported_exchanges;
$supportedCount = collect($supportResults)->where('supported', true)->count();
$totalCount = count($supportResults);
$supportStatus = [
'results' => $supportResults,
'supported_count' => $supportedCount,
'total_count' => $totalCount,
'status' => match(true) {
$supportedCount >= 3 => 'excellent',
$supportedCount >= 2 => 'good',
$supportedCount >= 1 => 'limited',
default => 'unsupported'
}
];
}
return view('admin.coins.show', compact('coin', 'supportStatus'));
}
/**
* Show the form for editing the specified coin
*/
public function edit(Coin $coin): View
{
return view('admin.coins.edit', compact('coin'));
}
/**
* Update the specified coin
*/
public function update(Request $request, Coin $coin): RedirectResponse
{
$validated = $this->validateCoin($request, $coin->id);
try {
// Handle image upload/URL if provided
if ($request->filled('image_url') || $request->hasFile('image_file')) {
$validated['image'] = $this->handleImageUpload($request);
}
$coin->update($validated);
// Check exchange support if requested
if ($request->boolean('check_support')) {
$this->supportChecker->updateCoinSupportStatus($coin);
}
return redirect()
->route('admin.coins.index')
->with('success', "Coin '{$coin->name}' updated successfully!");
} catch (\Exception $e) {
Log::error('Error updating coin', [
'coin_id' => $coin->id,
'error' => $e->getMessage(),
'data' => $validated
]);
return back()
->withInput()
->with('error', 'Failed to update coin: ' . $e->getMessage());
}
}
/**
* Remove the specified coin
*/
public function destroy(Coin $coin): RedirectResponse
{
try {
$coinName = $coin->name;
$coin->delete();
return redirect()
->route('admin.coins.index')
->with('success', "Coin '{$coinName}' deleted successfully!");
} catch (\Exception $e) {
Log::error('Error deleting coin', [
'coin_id' => $coin->id,
'error' => $e->getMessage()
]);
return back()
->with('error', 'Failed to delete coin: ' . $e->getMessage());
}
}
/**
* Check support for a specific coin (AJAX)
*/
public function checkSupport(Coin $coin): JsonResponse
{
try {
$result = $this->supportChecker->updateCoinSupportStatus($coin);
return response()->json([
'success' => true,
'data' => $result
]);
} catch (\Exception $e) {
Log::error('Error checking support for coin', [
'coin_id' => $coin->id,
'error' => $e->getMessage()
]);
return response()->json([
'success' => false,
'message' => 'Failed to check support: ' . $e->getMessage()
], 500);
}
}
/**
* Bulk check support for multiple coins (AJAX)
*/
public function bulkCheckSupport(Request $request): JsonResponse
{
$request->validate([
'coin_ids' => 'required|array',
'coin_ids.*' => 'exists:coins,id'
]);
try {
$results = [];
$coins = Coin::whereIn('id', $request->coin_ids)->get();
foreach ($coins as $coin) {
$results[] = $this->supportChecker->updateCoinSupportStatus($coin);
}
return response()->json([
'success' => true,
'data' => $results
]);
} catch (\Exception $e) {
Log::error('Error in bulk support check', [
'coin_ids' => $request->coin_ids,
'error' => $e->getMessage()
]);
return response()->json([
'success' => false,
'message' => 'Failed to check support: ' . $e->getMessage()
], 500);
}
}
/**
* Toggle coin active status (AJAX)
*/
public function toggleStatus(Coin $coin): JsonResponse
{
try {
$coin->update(['is_active' => !$coin->is_active]);
return response()->json([
'success' => true,
'is_active' => $coin->is_active,
'message' => "Coin {$coin->name} " . ($coin->is_active ? 'activated' : 'deactivated')
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Failed to toggle status: ' . $e->getMessage()
], 500);
}
}
/**
* Clear crypto service cache
*/
public function clearCache(): RedirectResponse
{
try {
$this->cryptoService->clearCache();
return back()->with('success', 'Cache cleared successfully!');
} catch (\Exception $e) {
return back()->with('error', 'Failed to clear cache: ' . $e->getMessage());
}
}
/**
* Validate coin data
*/
private function validateCoin(Request $request, ?int $coinId = null): array
{
$rules = [
'symbol' => 'required|string|max:10|alpha',
'name' => 'required|string|max:100',
'network' => 'nullable|string|max:20',
'display_symbol' => 'required|string|max:20',
'price_symbol' => 'required|string|max:50|alpha_dash',
'decimals' => 'required|integer|min:0|max:18',
'default_fee' => 'required|numeric|min:0',
'default_swap_fee' => 'required|numeric|min:0',
'address_validation_regex' => 'required|string|max:500',
'sort_order' => 'required|integer|min:1',
'is_active' => 'boolean',
'use_manual_price' => 'boolean',
'manual_price_usd' => 'nullable|numeric|min:0',
'image_url' => 'nullable|url|max:500',
'image_file' => 'nullable|image|mimes:png,jpg,jpeg,svg|max:2048'
];
// Add unique validation for symbol+network combination
$symbolRule = 'unique:coins,symbol';
if ($coinId) {
$symbolRule .= ",{$coinId}";
}
if ($request->filled('network')) {
$symbolRule .= ',id,network,' . $request->network;
}
$rules['symbol'] .= '|' . $symbolRule;
return $request->validate($rules);
}
/**
* Handle image upload or URL
*/
private function handleImageUpload(Request $request): ?string
{
// Priority: file upload > URL
if ($request->hasFile('image_file')) {
$file = $request->file('image_file');
$filename = time() . '_' . $file->getClientOriginalName();
$path = $file->storeAs('coins', $filename, 'public');
return $path;
}
if ($request->filled('image_url')) {
return $request->image_url;
}
return null;
}
}
b IDATxytVսϓ22 A@IR:hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-E