Add HL3D branding, Etsy Live/Draft paging and Projects lifecycle
Deploy HickLab3D Dashboard / Deploy to production (push) Failing after 23s
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.
This commit is contained in:
+36
-11
@@ -5,6 +5,7 @@ namespace App\Services;
|
||||
use App\Models\EtsyAppCredential;
|
||||
use App\Models\EtsyConnection;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
@@ -188,24 +189,34 @@ class Etsy
|
||||
/**
|
||||
* @return array{count: int, limit: int, offset: int, state: string, results: list<array<string, mixed>>}
|
||||
*/
|
||||
public function listListings(EtsyConnection $connection, string $state = 'active', int $limit = 25, int $offset = 0): array
|
||||
public function listListings(EtsyConnection $connection, string $state = 'active', int $limit = 25, int $offset = 0, bool $fresh = false): array
|
||||
{
|
||||
$state = strtolower($state);
|
||||
if (! in_array($state, self::LISTING_STATES, true)) {
|
||||
throw new RuntimeException('Listing state must be one of: '.implode(', ', self::LISTING_STATES).'.');
|
||||
}
|
||||
[$limit, $offset] = $this->page($limit, $offset);
|
||||
$response = $this->request('GET', 'application/shops/'.$connection->shop_id.'/listings', $this->accessToken($connection), [
|
||||
'state' => $state, 'limit' => $limit, 'offset' => $offset,
|
||||
]);
|
||||
$generation = Cache::get('etsy.listing-generation.'.$connection->shop_id, '0');
|
||||
$cacheKey = 'etsy.listListings.'.$connection->shop_id.'.'.$generation.'.'.$state.'.'.$limit.'.'.$offset;
|
||||
|
||||
return [
|
||||
'count' => (int) ($response['count'] ?? count($response['results'] ?? [])),
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
'state' => $state,
|
||||
'results' => array_map(fn (array $listing): array => $this->summarizeListing($listing), $this->results($response)),
|
||||
];
|
||||
$result = $this->cache->remember($cacheKey, 600, function () use ($connection, $state, $limit, $offset): array {
|
||||
$response = $this->request('GET', 'application/shops/'.$connection->shop_id.'/listings', $this->accessToken($connection), [
|
||||
'state' => $state, 'limit' => $limit, 'offset' => $offset,
|
||||
]);
|
||||
|
||||
return [
|
||||
'count' => (int) ($response['count'] ?? count($response['results'] ?? [])),
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
'state' => $state,
|
||||
'results' => array_map(fn (array $listing): array => $this->summarizeListing($listing), $this->results($response)),
|
||||
];
|
||||
}, $fresh);
|
||||
$entry = Cache::get('integration:'.$cacheKey);
|
||||
$result['fetched_at'] = is_array($entry) ? $entry['fetched_at'] : null;
|
||||
$result['stale'] = $result['fetched_at'] === null || time() >= $result['fetched_at'] + 600;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,6 +281,7 @@ class Etsy
|
||||
$payload = $this->validatedListingPayload($payload, true);
|
||||
$listing = $this->request('POST', 'application/shops/'.$connection->shop_id.'/listings', $this->accessToken($connection), body: $payload);
|
||||
$this->cache->forget('etsy.listings.'.$connection->shop_id);
|
||||
$this->forgetListingStateCaches($connection);
|
||||
|
||||
return $this->summarizeListing($listing, detailed: true);
|
||||
}
|
||||
@@ -289,6 +301,7 @@ class Etsy
|
||||
$listing = $this->request('PATCH', 'application/shops/'.$connection->shop_id.'/listings/'.$listingId, $this->accessToken($connection), body: $payload);
|
||||
$this->assertListingBelongsToShop($connection, $listing);
|
||||
$this->cache->forget('etsy.listings.'.$connection->shop_id);
|
||||
$this->forgetListingStateCaches($connection);
|
||||
|
||||
return $this->summarizeListing($listing, detailed: true);
|
||||
}
|
||||
@@ -306,10 +319,21 @@ class Etsy
|
||||
$listing = $this->request('PATCH', 'application/shops/'.$connection->shop_id.'/listings/'.$listingId, $this->accessToken($connection), body: ['state' => 'active']);
|
||||
$this->assertListingBelongsToShop($connection, $listing);
|
||||
$this->cache->forget('etsy.listings.'.$connection->shop_id);
|
||||
$this->forgetListingStateCaches($connection);
|
||||
|
||||
return $this->summarizeListing($listing, detailed: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the first page of both Live and Draft state caches so a
|
||||
* write immediately shown to the operator doesn't keep serving a stale
|
||||
* cached page. Older pages expire on their own TTL.
|
||||
*/
|
||||
private function forgetListingStateCaches(EtsyConnection $connection): void
|
||||
{
|
||||
Cache::put('etsy.listing-generation.'.$connection->shop_id, bin2hex(random_bytes(16)), 172800);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
@@ -359,6 +383,7 @@ class Etsy
|
||||
}
|
||||
$inventory = $this->request('PUT', 'application/listings/'.$listingId.'/inventory', $this->accessToken($connection), body: $body, jsonBody: true);
|
||||
$this->cache->forget('etsy.listings.'.$connection->shop_id);
|
||||
$this->forgetListingStateCaches($connection);
|
||||
|
||||
return $inventory;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user