Deploy HickLab3D Dashboard / Deploy to production (push) Failing after 23s
Replace the old touchmark/PNG identity with the supplied lockup and monogram, segmented role-aware navigation, Live/Draft listing filters with generation-based cache invalidation, and admin Projects covering immutable file revisions, listing live pointers, and exact-decimal filament/packaging/shipping costing. Additive schema only; no Etsy publication. Isolated clone tests: 301 tests, 1162 assertions.
119 lines
4.5 KiB
PHP
119 lines
4.5 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers;
|
||
|
||
use App\Models\EtsyConnection;
|
||
use App\Services\Etsy;
|
||
use Illuminate\Http\RedirectResponse;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\View\View;
|
||
use Throwable;
|
||
|
||
class EtsyController extends Controller
|
||
{
|
||
/** @var list<string> */
|
||
private const UI_STATES = ['active', 'draft'];
|
||
|
||
private const PER_PAGE = 20;
|
||
|
||
public function index(Request $request, Etsy $etsy): View
|
||
{
|
||
abort_unless($request->user()->role === 'admin', 403);
|
||
$connection = EtsyConnection::first();
|
||
$receipts = [];
|
||
$listings = [];
|
||
$listingCount = 0;
|
||
$receiptsError = null;
|
||
$listingsError = null;
|
||
$stateError = null;
|
||
$pageError = null;
|
||
|
||
$rawState = $request->query('state', 'active');
|
||
$state = is_string($rawState) ? strtolower($rawState) : '';
|
||
if (! in_array($state, self::UI_STATES, true)) {
|
||
$stateError = 'This is not a supported listing state. Select Live or Drafts.';
|
||
$state = 'active';
|
||
}
|
||
|
||
$rawPage = $request->query('page', '1');
|
||
if (! is_string($rawPage) || ! ctype_digit($rawPage) || strlen($rawPage) > 5 || (int) $rawPage < 1 || (int) $rawPage > 10000) {
|
||
$pageError = 'This is not a valid page number. Use pages 1–10000.';
|
||
$page = 1;
|
||
} else {
|
||
$page = (int) $rawPage;
|
||
}
|
||
|
||
if ($connection && $etsy->configured()) {
|
||
try {
|
||
$receipts = $etsy->receipts($connection)['results'] ?? [];
|
||
} catch (Throwable $exception) {
|
||
report($exception);
|
||
$receiptsError = 'Order data is temporarily unavailable. Reconnect the shop if this continues.';
|
||
}
|
||
|
||
if (! $stateError && ! $pageError) {
|
||
try {
|
||
$listingPage = $etsy->listListings($connection, $state, self::PER_PAGE, (max(1, $page) - 1) * self::PER_PAGE);
|
||
$listings = $listingPage['results'];
|
||
$listingCount = $listingPage['count'];
|
||
} catch (Throwable $exception) {
|
||
report($exception);
|
||
$listingsError = 'Listing data is temporarily unavailable. Reconnect the shop if this continues.';
|
||
}
|
||
}
|
||
}
|
||
|
||
return view('etsy', [
|
||
'connection' => $connection,
|
||
'receipts' => $receipts,
|
||
'listings' => $listings,
|
||
'listingSnapshot' => $listingPage ?? null,
|
||
'listingCount' => $listingCount,
|
||
'state' => $state,
|
||
'page' => $page,
|
||
'perPage' => self::PER_PAGE,
|
||
'receiptsError' => $receiptsError,
|
||
'listingsError' => $listingsError,
|
||
'stateError' => $stateError,
|
||
'pageError' => $pageError,
|
||
'configured' => $etsy->configured(),
|
||
]);
|
||
}
|
||
|
||
public function connect(Request $request, Etsy $etsy): RedirectResponse
|
||
{
|
||
abort_unless($request->user()->role === 'admin', 403);
|
||
if (! $etsy->configured()) {
|
||
return back()->withErrors(['etsy' => 'Configure the Etsy app key and shared secret first.']);
|
||
}
|
||
|
||
$state = bin2hex(random_bytes(24));
|
||
$verifier = rtrim(strtr(base64_encode(random_bytes(48)), '+/', '-_'), '=');
|
||
$request->session()->put('etsy_oauth', ['state' => $state, 'verifier' => $verifier, 'started' => time()]);
|
||
|
||
return redirect()->away($etsy->authorizationUrl($state, $verifier));
|
||
}
|
||
|
||
public function callback(Request $request, Etsy $etsy): RedirectResponse
|
||
{
|
||
abort_unless($request->user()->role === 'admin', 403);
|
||
$pending = $request->session()->pull('etsy_oauth');
|
||
if (! is_array($pending) || time() - ($pending['started'] ?? 0) > 600
|
||
|| ! is_string($request->query('state'))
|
||
|| ! hash_equals($pending['state'] ?? '', $request->query('state'))
|
||
|| ! is_string($request->query('code'))) {
|
||
return redirect()->route('etsy.index')->withErrors(['etsy' => 'Etsy connection expired or was not verified. Please try again.']);
|
||
}
|
||
|
||
try {
|
||
$connection = $etsy->connect($request->query('code'), $pending['verifier']);
|
||
|
||
return redirect()->route('etsy.index')->with('status', $connection->shop_name.' connected to Etsy.');
|
||
} catch (Throwable $exception) {
|
||
report($exception);
|
||
|
||
return redirect()->route('etsy.index')->withErrors(['etsy' => 'Etsy rejected the connection. Check the app credentials and callback URL.']);
|
||
}
|
||
}
|
||
}
|