|
6279
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Hubspot;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Support\Facades\Log;
use Jiminny\Contracts\Services\Crm\Provider\HubspotInterface;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\BatchSyncRedisService;
use Jiminny\Services\ResolveTeamCrmConnection;
/**
* Shared logic for HubSpot batch import jobs.
*
* Provides common functionality for importing batches of HubSpot objects (deals, contacts, companies)
* with consistent error handling, logging, and failure recovery mechanisms.
*
* Failure Handling Strategy:
*
* 1. SYSTEM-LEVEL FAILURES (HubSpot API down, DB connection lost, timeout):
* - Exception propagates out of handle()
* - Laravel retries the job up to $tries times with $backoff delays
* - If all retries exhausted, failed() requeues ALL IDs back to Redis
* - Next SyncObjects cycle will pick them up and retry
*
* 2. DATA-LEVEL FAILURES (per-object exceptions during import):
* - Caught inside the per-object loop in import*BatchByIds() methods
* - Indicate permanent issues: DB constraints, corrupt data, unmappable values
* - Retrying won't fix them - logged with specific error per ID
* - NOT requeued to avoid infinite retry loops
* - Require manual investigation and data correction
*
* Note: Objects intentionally skipped (missing dependencies like account, unknown pipeline/stage)
* return null and are not considered failures - they simply cannot be imported until dependencies exist.
*/
trait ImportBatchJobTrait
{
private const string OBJECT_TYPE_CONTACT = 'contact';
private const string OBJECT_TYPE_COMPANY = 'company';
private const string OBJECT_TYPE_DEAL = 'deal';
private const int LOCK_EXPIRES_AFTER_SECONDS = 360;
private const string [ENV_SECRET];
private int $crmConfigurationId;
private array $crmIds;
abstract protected function getObjectType(): string;
abstract protected function getLogPrefix(): string;
abstract protected function importBatch(HubspotInterface $crmService, array $crmIds): array;
/**
* Serialize HubSpot import batches per CRM configuration across all object types.
*
* `shared()` makes the lock key omit the job class, so ImportOpportunityBatch,
* ImportContactBatch and ImportAccountBatch for the same crm_configuration_id
* contend on a single lock. This prevents concurrent imports from causing
* gap-lock contention on the `accounts` and `contacts` unique indexes.
*
* Duplicates are dropped (not released back to the queue). On SQS, each release
* increments the message's ApproximateReceiveCount, which Laravel reads as
* $job->attempts(). With $tries = 3 and lock-holders that can run for >45s,
* releasing duplicates causes them to exhaust their attempt budget while waiting
* and trip MaxAttemptsExceededException without ever running. Dropping is safe
* here because SyncObjects rediscovers IDs on every cycle, so any IDs carried
* by a dropped duplicate will be re-batched on the next sync.
*/
public function middleware(): array
{
return [
new HandleHubspotRateLimit(),
(new WithoutOverlapping(self::LOCK_KEY_PREFIX . $this->crmConfigurationId))
->shared()
->expireAfter(self::LOCK_EXPIRES_AFTER_SECONDS)
->dontRelease(),
];
}
protected function processImportBatch(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
CrmConfigurationRepository $crmConfigurationRepository,
CrmEntityRepository $crmEntityRepository
): void {
$crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);
if ($crmConfig === null) {
Log::error($this->getLogPrefix() . ' CRM configuration not found', [
'crmConfigurationId' => $this->crmConfigurationId,
]);
return;
}
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
if (! $crmService instanceof HubspotInterface) {
Log::error($this->getLogPrefix() . ' CRM service is not HubSpot', [
'crmConfigurationId' => $this->crmConfigurationId,
'serviceClass' => $crmService::class,
]);
return;
}
Log::info($this->getLogPrefix() . ' Processing batch', [
'crmConfigurationId' => $this->crmConfigurationId,
'batchSize' => count($this->crmIds),
]);
$filteredIds = $this->filterExistingObjects($crmConfig, $crmEntityRepository);
if (empty($filteredIds)) {
Log::info($this->getLogPrefix() . ' No existing objects found in Jiminny, skipping batch', [
'crmConfigurationId' => $this->crmConfigurationId,
'requestedCount' => count($this->crmIds),
]);
return;
}
$startTime = microtime(true);
$result = $this->importBatch($crmService, $filteredIds);
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$successCount = $result['success_count'] ?? \count($result['success'] ?? []);
$failedIds = $result['failed_ids'] ?? [];
$skippedCount = count($this->crmIds) - count($filteredIds);
Log::info($this->getLogPrefix() . ' Batch completed', [
'crmConfigurationId' => $this->crmConfigurationId,
'success' => $successCount,
'failed' => count($failedIds),
'skipped' => $skippedCount,
'requested' => count($this->crmIds),
'processed' => count($filteredIds),
'duration_ms' => $durationMs,
]);
if (! empty($failedIds)) {
Log::error($this->getLogPrefix() . ' Data-level failures require investigation', [
'crmConfigurationId' => $this->crmConfigurationId,
'failedCount' => \count($failedIds),
'failedIds' => $failedIds,
'errors' => $result['errors'] ?? [],
]);
}
}
/**
* Called by Laravel after all $tries are exhausted.
*
* System-level failure (HubSpot API down, timeout, OOM) - the entire handle() threw
* an unhandled exception on every attempt. Requeue ALL IDs back to Redis so the next
* SyncObjects cycle picks them up.
*
* This does NOT fire for data-level failures (those are caught in the per-object loop
* and logged without retrying).
*
* Requeue Strategy:
* - Each ID is individually requeued to Redis using SADD (automatic deduplication)
* - If requeue fails for specific IDs, they are logged as permanently lost
* - Redis requeue failures are rare (Redis connection issues) but critical
*/
protected function requeueFailedBatch(\Throwable $exception): void
{
$redisService = app(BatchSyncRedisService::class);
Log::error($this->getLogPrefix() . ' Job failed after all retries, requeuing IDs', [
'crmConfigurationId' => $this->crmConfigurationId,
'idCount' => \count($this->crmIds),
'error' => $exception->getMessage(),
]);
$requeued = 0;
$failedToRequeue = [];
foreach ($this->crmIds as $crmId) {
try {
$redisService->collectObjectIdToBatch(
$this->getObjectType(),
(string) $crmId,
$this->crmConfigurationId,
'all'
);
$requeued++;
} catch (\Throwable $e) {
$failedToRequeue[] = $crmId;
Log::warning($this->getLogPrefix() . ' Failed to requeue ID', [
'crmConfigurationId' => $this->crmConfigurationId,
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
}
Log::info($this->getLogPrefix() . ' Requeue completed after job failure', [
'crmConfigurationId' => $this->crmConfigurationId,
'requeued' => $requeued,
'failedToRequeue' => \count($failedToRequeue),
'total' => \count($this->crmIds),
]);
if (! empty($failedToRequeue)) {
Log::critical($this->getLogPrefix() . ' Permanently lost IDs after requeue failure', [
'crmConfigurationId' => $this->crmConfigurationId,
'failedIds' => $failedToRequeue,
]);
}
}
private function filterExistingObjects(Configuration $crmConfig, CrmEntityRepository $crmEntityRepository): array
{
$objectType = $this->getObjectType();
if ($objectType === self::OBJECT_TYPE_CONTACT) {
return $this->filterExistingContacts($crmConfig, $crmEntityRepository);
}
if ($objectType === self::OBJECT_TYPE_COMPANY) {
return $this->filterExistingAccounts($crmConfig, $crmEntityRepository);
}
return $this->crmIds;
}
private function filterExistingContacts(Configuration $crmConfig, CrmEntityRepository $crmEntityRepository): array
{
$existingContacts = $crmEntityRepository->getExistingContactCrmIds($crmConfig, $this->crmIds);
$existingIds = array_intersect($this->crmIds, $existingContacts);
return array_values($existingIds);
}
private function filterExistingAccounts(Configuration $crmConfig, CrmEntityRepository $crmEntityRepository): array
{
$existingAccounts = $crmEntityRepository->getExistingAccountCrmIds($crmConfig, $this->crmIds);
$existingIds = array_intersect($this->crmIds, $existingAccounts);
return array_values($existingIds);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options...
|
PhpStorm
|
faVsco.js – ImportBatchJobTrait.php
|
NULL
|
6279
|
|
6280
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm\Hubspot;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Support\Facades\Log;
use Jiminny\Contracts\Services\Crm\Provider\HubspotInterface;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\Crm\CrmConfigurationRepository;
use Jiminny\Repositories\Crm\CrmEntityRepository;
use Jiminny\Services\Crm\Hubspot\BatchSyncRedisService;
use Jiminny\Services\ResolveTeamCrmConnection;
/**
* Shared logic for HubSpot batch import jobs.
*
* Provides common functionality for importing batches of HubSpot objects (deals, contacts, companies)
* with consistent error handling, logging, and failure recovery mechanisms.
*
* Failure Handling Strategy:
*
* 1. SYSTEM-LEVEL FAILURES (HubSpot API down, DB connection lost, timeout):
* - Exception propagates out of handle()
* - Laravel retries the job up to $tries times with $backoff delays
* - If all retries exhausted, failed() requeues ALL IDs back to Redis
* - Next SyncObjects cycle will pick them up and retry
*
* 2. DATA-LEVEL FAILURES (per-object exceptions during import):
* - Caught inside the per-object loop in import*BatchByIds() methods
* - Indicate permanent issues: DB constraints, corrupt data, unmappable values
* - Retrying won't fix them - logged with specific error per ID
* - NOT requeued to avoid infinite retry loops
* - Require manual investigation and data correction
*
* Note: Objects intentionally skipped (missing dependencies like account, unknown pipeline/stage)
* return null and are not considered failures - they simply cannot be imported until dependencies exist.
*/
trait ImportBatchJobTrait
{
private const string OBJECT_TYPE_CONTACT = 'contact';
private const string OBJECT_TYPE_COMPANY = 'company';
private const string OBJECT_TYPE_DEAL = 'deal';
private const int LOCK_EXPIRES_AFTER_SECONDS = 360;
private const string [ENV_SECRET];
private int $crmConfigurationId;
private array $crmIds;
abstract protected function getObjectType(): string;
abstract protected function getLogPrefix(): string;
abstract protected function importBatch(HubspotInterface $crmService, array $crmIds): array;
/**
* Serialize HubSpot import batches per CRM configuration across all object types.
*
* `shared()` makes the lock key omit the job class, so ImportOpportunityBatch,
* ImportContactBatch and ImportAccountBatch for the same crm_configuration_id
* contend on a single lock. This prevents concurrent imports from causing
* gap-lock contention on the `accounts` and `contacts` unique indexes.
*
* Duplicates are dropped (not released back to the queue). On SQS, each release
* increments the message's ApproximateReceiveCount, which Laravel reads as
* $job->attempts(). With $tries = 3 and lock-holders that can run for >45s,
* releasing duplicates causes them to exhaust their attempt budget while waiting
* and trip MaxAttemptsExceededException without ever running. Dropping is safe
* here because SyncObjects rediscovers IDs on every cycle, so any IDs carried
* by a dropped duplicate will be re-batched on the next sync.
*/
public function middleware(): array
{
return [
new HandleHubspotRateLimit(),
(new WithoutOverlapping(self::LOCK_KEY_PREFIX . $this->crmConfigurationId))
->shared()
->expireAfter(self::LOCK_EXPIRES_AFTER_SECONDS)
->dontRelease(),
];
}
protected function processImportBatch(
ResolveTeamCrmConnection $resolveTeamCrmConnection,
CrmConfigurationRepository $crmConfigurationRepository,
CrmEntityRepository $crmEntityRepository
): void {
$crmConfig = $crmConfigurationRepository->findById($this->crmConfigurationId);
if ($crmConfig === null) {
Log::error($this->getLogPrefix() . ' CRM configuration not found', [
'crmConfigurationId' => $this->crmConfigurationId,
]);
return;
}
$crmService = $resolveTeamCrmConnection->resolveForTeam($crmConfig->getTeam());
if (! $crmService instanceof HubspotInterface) {
Log::error($this->getLogPrefix() . ' CRM service is not HubSpot', [
'crmConfigurationId' => $this->crmConfigurationId,
'serviceClass' => $crmService::class,
]);
return;
}
Log::info($this->getLogPrefix() . ' Processing batch', [
'crmConfigurationId' => $this->crmConfigurationId,
'batchSize' => count($this->crmIds),
]);
$filteredIds = $this->filterExistingObjects($crmConfig, $crmEntityRepository);
if (empty($filteredIds)) {
Log::info($this->getLogPrefix() . ' No existing objects found in Jiminny, skipping batch', [
'crmConfigurationId' => $this->crmConfigurationId,
'requestedCount' => count($this->crmIds),
]);
return;
}
$startTime = microtime(true);
$result = $this->importBatch($crmService, $filteredIds);
$durationMs = round((microtime(true) - $startTime) * 1000, 2);
$successCount = $result['success_count'] ?? \count($result['success'] ?? []);
$failedIds = $result['failed_ids'] ?? [];
$skippedCount = count($this->crmIds) - count($filteredIds);
Log::info($this->getLogPrefix() . ' Batch completed', [
'crmConfigurationId' => $this->crmConfigurationId,
'success' => $successCount,
'failed' => count($failedIds),
'skipped' => $skippedCount,
'requested' => count($this->crmIds),
'processed' => count($filteredIds),
'duration_ms' => $durationMs,
]);
if (! empty($failedIds)) {
Log::error($this->getLogPrefix() . ' Data-level failures require investigation', [
'crmConfigurationId' => $this->crmConfigurationId,
'failedCount' => \count($failedIds),
'failedIds' => $failedIds,
'errors' => $result['errors'] ?? [],
]);
}
}
/**
* Called by Laravel after all $tries are exhausted.
*
* System-level failure (HubSpot API down, timeout, OOM) - the entire handle() threw
* an unhandled exception on every attempt. Requeue ALL IDs back to Redis so the next
* SyncObjects cycle picks them up.
*
* This does NOT fire for data-level failures (those are caught in the per-object loop
* and logged without retrying).
*
* Requeue Strategy:
* - Each ID is individually requeued to Redis using SADD (automatic deduplication)
* - If requeue fails for specific IDs, they are logged as permanently lost
* - Redis requeue failures are rare (Redis connection issues) but critical
*/
protected function requeueFailedBatch(\Throwable $exception): void
{
$redisService = app(BatchSyncRedisService::class);
Log::error($this->getLogPrefix() . ' Job failed after all retries, requeuing IDs', [
'crmConfigurationId' => $this->crmConfigurationId,
'idCount' => \count($this->crmIds),
'error' => $exception->getMessage(),
]);
$requeued = 0;
$failedToRequeue = [];
foreach ($this->crmIds as $crmId) {
try {
$redisService->collectObjectIdToBatch(
$this->getObjectType(),
(string) $crmId,
$this->crmConfigurationId,
'all'
);
$requeued++;
} catch (\Throwable $e) {
$failedToRequeue[] = $crmId;
Log::warning($this->getLogPrefix() . ' Failed to requeue ID', [
'crmConfigurationId' => $this->crmConfigurationId,
'crmId' => $crmId,
'error' => $e->getMessage(),
]);
}
}
Log::info($this->getLogPrefix() . ' Requeue completed after job failure', [
'crmConfigurationId' => $this->crmConfigurationId,
'requeued' => $requeued,
'failedToRequeue' => \count($failedToRequeue),
'total' => \count($this->crmIds),
]);
if (! empty($failedToRequeue)) {
Log::critical($this->getLogPrefix() . ' Permanently lost IDs after requeue failure', [
'crmConfigurationId' => $this->crmConfigurationId,
'failedIds' => $failedToRequeue,
]);
}
}
private function filterExistingObjects(Configuration $crmConfig, CrmEntityRepository $crmEntityRepository): array
{
$objectType = $this->getObjectType();
if ($objectType === self::OBJECT_TYPE_CONTACT) {
return $this->filterExistingContacts($crmConfig, $crmEntityRepository);
}
if ($objectType === self::OBJECT_TYPE_COMPANY) {
return $this->filterExistingAccounts($crmConfig, $crmEntityRepository);
}
return $this->crmIds;
}
private function filterExistingContacts(Configuration $crmConfig, CrmEntityRepository $crmEntityRepository): array
{
$existingContacts = $crmEntityRepository->getExistingContactCrmIds($crmConfig, $this->crmIds);
$existingIds = array_intersect($this->crmIds, $existingContacts);
return array_values($existingIds);
}
private function filterExistingAccounts(Configuration $crmConfig, CrmEntityRepository $crmEntityRepository): array
{
$existingAccounts = $crmEntityRepository->getExistingAccountCrmIds($crmConfig, $this->crmIds);
$existingIds = array_intersect($this->crmIds, $existingAccounts);
return array_values($existingIds);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ImportBatchJobTrait.php
|
NULL
|
6280
|
|
6281
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}...
|
PhpStorm
|
faVsco.js – SyncOpportunitiesJob.php
|
NULL
|
6281
|
|
6282
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
iTerm2ShellEditViewSessionScriptsProfilesWindowHelplahl100% <478Thu 7 May 21:08:31STAGE (ssh)*3181DOCKERDEV (-zsh)882APP (-zsh)DOCKER (-zsh)docker_lamp_1docker_lamp_12026-05-07 14:30:06 Running ['artisan'meeting-bot:schedule-bot] ….6S DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan'meeting-bot: schedule-bot > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:13 Running ['artisan'dialers:monitor-activities]4sDONEdocker_1amp_11 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:17 Running ['artisan' jiminny:monitor-social-accountSJ3s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:20 Running ['artisan' mailbox:skip-lists:refresh]2sDONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox: skip-lists:refresh › */proc/1/fd/1'2>&1docker_lamp_112026-05-07 14:30:23 Running ['artisan' mailbox:batch:process --max-batches=15]2s DONEdocker_Lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batches=15 >*/proc/1/fd/1' 2>&1docker_lamp_12026-05-07 14:30:25 Running ['artisan' conference:monitor: count]1S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' conference:monitor: count › */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:27 Running ['artisan' activity:purge-stale]2S DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan' activity:purge-stale › '/proc/1/fd/12>&1docker_lamp_1docker_lamp_1docker_lamp_12026-05-07 14:30:30 Running ['artisan' mailbox:text-relay:sync] {"error":"invalid_request""error_description": "Invalidimpersonation \u0026quot; sub\u0026quot;field: @"docker_1amp_11}docker_lamp_1docker_lamp_11 '/usr/local/bin/php' 'artisan' mailbox:text-relay:sync › */proc/1/fd/1' 2>&1docker_1amp_12026-05-07 14:30:35 Running ['artisan'conference:pre-meeting-notification]10s DONEdocker_1amp_111 '/usr/local/bin/php' 'artisan' conference:pre-meeting-notification'/proc/1/fd/1' 2>&1unexpected EOFukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/infrastructure/dev/docker (develop) $-zsh• 84screenpipe*Y2PROD (ssh)New release '24.04.4 LTS' available.Run 'do-release-upgrade' to upgrade to it.• *5-zsh|*** System restart required ***Last login: Mon Apr 27 07:45:27 2026 from 212.5.153.87X L3 EU (-zsh)Last login: Thu May 7 09:29:14 on consolePoetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|T4STAGE (ssh)Run 'do-release-upgrade' to upgrade to it.*** System restart required ***Last login: Tue Apr 28 06:25:10 2026 from 212.5.153.87in:-$XIT5QA (-zsh)Last login: Thu May 7 09:44:56on ttys002Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parents$X 16FE (-zsh)Last login: Thu May 7 09:44:56on ttys004₴6+PRODSTAGEPoetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I17 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|...
|
PhpStorm
|
faVsco.js – SyncOpportunitiesJob.php
|
NULL
|
6282
|
|
6283
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Middleware;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
/**
* Job middleware that catches RateLimitException from HubSpot API calls
* and releases the job back to the queue with the appropriate delay.
*
* This ensures that when HubSpot rate limits are hit (either proactively
* via ProviderRateLimiter or reactively via 429 responses), the job
* will be retried after the correct delay rather than failing immediately.
*/
class HandleHubspotRateLimit
{
private const int MAX_RETRY_DELAY = 600; // 10 minutes max
private const int MIN_RETRY_DELAY = 1; // 1 second min
public function handle(object $job, callable $next): void
{
try {
$next($job);
} catch (RateLimitException $e) {
$retryAfter = $e->getRetryAfter();
// Clamp delay to reasonable bounds
$delay = max(self::MIN_RETRY_DELAY, min($retryAfter, self::MAX_RETRY_DELAY));
Log::info('[HandleHubspotRateLimit] Rate limit caught, releasing job with delay', [
'job_class' => $job::class,
'retry_after_requested' => $retryAfter,
'retry_after_clamped' => $delay,
'rate_limit_message' => $e->getMessage(),
]);
// Release the job back to the queue with the calculated delay
$job->release($delay);
}
}
}
Project...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimit.php
|
NULL
|
6283
|
|
6284
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Middleware;
use Illuminate\Support\Facades\Log;
use Jiminny\Exceptions\RateLimitException;
/**
* Job middleware that catches RateLimitException from HubSpot API calls
* and releases the job back to the queue with the appropriate delay.
*
* This ensures that when HubSpot rate limits are hit (either proactively
* via ProviderRateLimiter or reactively via 429 responses), the job
* will be retried after the correct delay rather than failing immediately.
*/
class HandleHubspotRateLimit
{
private const int MAX_RETRY_DELAY = 600; // 10 minutes max
private const int MIN_RETRY_DELAY = 1; // 1 second min
public function handle(object $job, callable $next): void
{
try {
$next($job);
} catch (RateLimitException $e) {
$retryAfter = $e->getRetryAfter();
// Clamp delay to reasonable bounds
$delay = max(self::MIN_RETRY_DELAY, min($retryAfter, self::MAX_RETRY_DELAY));
Log::info('[HandleHubspotRateLimit] Rate limit caught, releasing job with delay', [
'job_class' => $job::class,
'retry_after_requested' => $retryAfter,
'retry_after_clamped' => $delay,
'rate_limit_message' => $e->getMessage(),
]);
// Release the job back to the queue with the calculated delay
$job->release($delay);
}
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – HandleHubspotRateLimit.php
|
NULL
|
6284
|
|
6285
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Contracts\Services\Crm\SearchTaskInterface;
use Jiminny\Exceptions\CrmUpdateException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Integrations\PlaybookResolver;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity\Provider;
use Jiminny\Models\Crm\Field;
use Jiminny\Models;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Jiminny\Services\Crm\CrmObjects\CrmObjectsManager;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
use Jiminny\Models\Feature\FeatureEnum;
class HydrateCrmDataByExternalCallIdJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
private LoggerInterface $logger;
private int $activityId;
private Models\Team $team;
private ?Models\Playbook $playbook;
private string $crmCallIdName;
private ?string $activityTypeField;
private SearchTaskInterface $crmService;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(int $activityId)
{
$this->activityId = $activityId;
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
ProviderRegistry $providerRegistry,
ActivityRepository $activityRepository,
LoggerInterface $logger
): void {
$this->logger = $logger;
$activity = $activityRepository->findById($this->activityId);
// find the crm record based on the external call id
try {
$this->validateAndInitiate($activity);
$crmService = $providerRegistry->get($this->getCrmProvider($activity));
if (! $crmService instanceof SearchTaskInterface) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Crm Provider not supported', [
'activityId' => $this->activityId,
'crmProvider' => $crmService->getDisplayName(),
]);
return;
}
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$fields = $this->buildFields();
$filters = $this->buildFilters($activity);
$task = $this->crmService->getTaskByFilterConditions($fields, $filters);
if ($task === null) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Task not found', [
'activityId' => $this->activityId,
'filters' => $filters,
]);
return;
}
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Task found', [
'activityId' => $this->activityId,
'task' => $task,
]);
} catch (CrmUpdateException $exception) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Activity not applicable, skipping', [
'activityId' => $this->activityId,
'callId' => $activity->getTelephonyProviderId(),
'reason' => $exception->getMessage(),
]);
return;
} catch (ModelNotFoundException $exception) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Activity not found', [
'activityId' => $this->activityId,
'reason' => $exception->getMessage(),
]);
return;
}
// save the crm data to the activity
$this->updateActivity($activity, $task);
// optional update crm record with jiminny data
}
/**
* @throws CrmUpdateException
*/
private function validateAndInitiate(?Models\Activity $activity): bool
{
if (! $activity instanceof Models\Activity) {
throw new ModelNotFoundException('Activity not found');
}
if (! $activity->hasUser()) {
throw new CrmUpdateException('No user assigned to activity');
}
$user = $activity->getUser();
$this->team = $user->getTeam();
if (! $this->team->hasFeature(FeatureEnum::SUPPORTS_UPDATE_BY_EXTERNAL_CALL_ID)) {
throw new CrmUpdateException('Feature flag not enabled');
}
$diallerProvider = $this->getDiallerProvider($activity);
if (! $this->isSupportedDiallerProvider($diallerProvider)) {
throw new CrmUpdateException('Dialler provider not supported');
}
if (! $activity->hasTelephonyProviderId()) {
throw new CrmUpdateException('No telephony_provider_id on activity');
}
$this->playbook = $this->getPlaybook($user);
if (! $this->playbook instanceof Models\Playbook) {
throw new CrmUpdateException('User has no playbook');
}
$this->activityTypeField = $this->getActivityTypeField();
if ($this->activityTypeField === null) {
throw new CrmUpdateException('No activity field on playbook');
}
$provider = $this->team->getProvider($diallerProvider);
if (
! $provider instanceof Provider ||
! $provider->hasCapabilities() ||
! isset($provider->getCapabilities()['crmCallId'])
) {
throw new CrmUpdateException('No crmCallId assigned to activity_provider');
}
$this->crmCallIdName = (string) $provider->getCapabilities()['crmCallId'];
return true;
}
private function isSupportedDiallerProvider(string $diallerProvider): bool
{
return $diallerProvider === Provider::PROVIDER_AMAZON_CONNECT;
}
private function getDiallerProvider(Models\Activity $activity): string
{
return $activity->getProvider();
}
private function getCrmProvider(Models\Activity $activity): string
{
return $activity->getCrm()->getProviderName();
}
private function getPlaybook(Models\User $user): ?Models\Playbook
{
$playbookResolver = app(PlaybookResolver::class);
return $playbookResolver->resolvePlaybookByUser($user);
}
private function buildFields(): array
{
$fields = $this->crmService->buildTaskSearchFields();
$fields[] = $this->activityTypeField;
return $fields;
}
private function getActivityTypeField(): ?string
{
$activityField = $this->playbook->getActivityField();
if (! $activityField instanceof Field) {
return null;
}
return $activityField->getCrmProviderId();
}
private function buildFilters(Models\Activity $activity): array
{
return [$this->crmCallIdName => $activity->getTelephonyProviderId()];
}
private function updateActivity($activity, $task): void
{
$activityData = $this->prepareActivityData($activity, $task);
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Updating activity', [
'activityId' => $this->activityId,
'activityData' => $activityData,
]);
$activity->update($activityData);
}
private function prepareActivityData(Models\Activity $activity, array $task): array
{
$activityData['crm_provider_id'] = $activity->crm_provider_id ?? $task['Id'];
if ($task[$this->activityTypeField] && $activity->getPlaybookCategoryId() === null) {
$playbookCategoryRepository = app(PlaybookCategoryRepository::class);
$playbookCategory = $playbookCategoryRepository->getPlaybookCategoryByName(
$this->playbook,
$task[$this->activityTypeField]
);
if ($playbookCategory instanceof Models\PlaybookCategory) {
$activityData['playbook_category_id'] = $playbookCategory->getId();
}
}
if (! $activity->hasProspect()) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Activity has no prospect', [
'activityId' => $this->activityId,
]);
$activityData = $this->attachProspectData($activityData, $task);
}
return $activityData;
}
private function attachProspectData(array $activityData, array $task): array
{
$crmObjects = $this->crmService->mapCrmObjects($task);
$crmObjectManager = app(CrmObjectsManager::class, ['config' => $this->team->getCrmConfiguration()]);
foreach ($crmObjects as $type => $crmId) {
$crmObject = $crmObjectManager->findByCrmProviderId($type, $crmId);
if ($crmObject !== null) {
$activityField = $type . '_id';
$activityData[$activityField] = $crmObject->getId();
}
}
return $activityData;
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – HydrateCrmDataByExternalCallIdJob.php
|
NULL
|
6285
|
|
6286
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
PhostormVIewINavigareCodeFV faVsco.js?9 masterProiect© DetachActivityObject.phpv DCrm©syncopportunitesJoo.ong© Client.phpv D Deletevw-Wcaeeeuinirlele.© DeleteContactJob.fy veletecrmenuly wrec) DeleteLeadJob.ong© DeleteOpportunityJ © Hubspot/Service.phpC) PayloadBuilder.php(©) CrmActivityService.phpC) CachedCrmServiceDecorator.phoc) vertyacuivitycrmi:Hubspot/..SyncCrmEntitiesTrait.pho(C) Pipedrive/Service.phpv@ HubspotOpportunitySyncTest.php>C TraitsC FetchMergedObjecc) HubsporAppuninst© ImportAccountBatctImpor barchJoolra© ImportContactBatcc) ImportOpportunitv:( ProcessHubspotWeC) ProcessinternalWelC) ProcessMerdedooiC) ProcessWeohookevC) UodateDealWebho> M Salesforce(C) AutoloaDelavedToCrm(C) CheckAndRetrvRemoti(C) CreateFollowuoActivit(c) CroateNotec nhnl(c) MatchActivitiocToNew(C) MatchA ctivitvCrmDatal© NoteObject.php© SaveActivity.php© SaveTranscription.php© SetupLayout.php© SyncActivity.php© SyncFieldMetadata.ph© SyncHubspotObjects.r© SyncLeads.php© SyncObjects.php© SyncOpportunities.Job© SyncOpportunitv.phc© SyncProfileMetadata.nc) Svncireampields.00.o©SvncTeamMetadata.ol(C) Uodate@ooortunitvSoM DealRisksM Meetina3o1M Middleward(C) Patel imited nhn> M StreaminalServicelntertace.phpk?phpdeciare (strict tvnesai):namespace Jiminny \Jobs\Activity \Import;use Illuminate \Bus\Queueableuse Illuminate\Contracts\Queue\ShouldQueue;use eecuminace roundaczon bus uispacchaole.uce Tlluminate Queue Minteractswithuveue,use Jiminhy component uueve conscantsuse Jininny contracts services uri searchlaskenterrace.use viminny cxcepcions crnupdarecxceptionuse Jiminny Exceptions Modelnotroundexception:use Jiminny Inteqrations Playbookkesolver:luse Jiminny Jobs Middleware HandleHubspotRateLimit:use Jiminny Models Activity Provider:use Jiminny Models Crm rield:use Jaminny rodels.use Jiminny Renositories ActivitvRevository:use Jaminny Renositories PlavbookCategorvRenository:use Jiminny Services Crm Crmobiects Crmobiectslanager:Jaminny Services Crm ProviderRegistry:use Psr\Log \LoggerInterface;use Jiminny Models Feature Featureenum.class HvdrateCrmDataßvExternalCal1Td.Joh imolements ShouldQueueuse Disnatchable:use InteractsWithQueue;use Queueable:private LoggerInterface $logger:private int SactivityId;private ModelsTeam $team:private ?Models\Playbook $playbook:private string scrmuallidame;,ACCept Renect© ProviderRateLimiter.php X = custom.logElaravel.log4 SF jiminny@localhost]A HS_local jiminny@localhost]tiò accounts fimA console (PROD]# console [eu)A console [STAGING]›use ..•class Providerratelimitenorotected RateLimiter SrateLimiter:public function __construct(RateLimiter SrateLimiter)l...hpublic function canMakeRequest(RateLimited $provider): bool/** Ovar RateLimitInterface $rateLimit */foreach (Sprovider->getRateLimits as $rateLimit) ‹$key = SrateLimit->getKeyO:if (Sthis->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {return true:pubLic tunction requestava1lableln kateLimited Sprovider: 1ntreturn Sprovider->getRateLimits@->isNotEmptv@Sprovider->getRateLimitso(RateLimitInterface SrateLimit): int => Sthis->rateLimiter->availableIn(SrateLir->maxonublic function incrementReauestCountRateLimited Soroviden): void** Avan Ratel imitIntenface Sratel imit *^foreach (Sprovider->getRateLimits as $rateLimit) ‹Sthicesnatmit-›getKey, $rateLimit->getWindow0):100% 12Thu 7 May 21:08:44AskJiminnyReportActivityServiceTest vCascadeHubspot Rate Limiting• Hubspot Rate Limit• ap /Servs/ts/cs/ndar/Yomman8/.mportParticapants.phpprivate functcon te rospeetbataßyeratCt78t8yg 31): 7array+0 ..cp /Srvs/skas//2achey/emg s6, groec-oraifr-pfton natchExactlyByEna 1)/function natchbyDoratnv*pubtic function matchEybontynYstring semag , ant suset gunud : arrayzarray• cd /Users/lukas/jiminny/app && grep -n "function matchByProspectIdentifier196г1private function matchByProspectIdentifier(Command cd, grep. heado ce dusers/lukas/jiminny/app && grep -n "findByProspectIdentifier" app/Services/Crm/ProspectCache-php |A You've used 91% of your quota. Quota resets May 8, 11:00 AM GMT+3.app/Jobs/Crm/ MatchActivityCrmData.php +6app/Jobs/Activity/ ConferenceCrmMatcherJob.php +6aoo/lobs/Crm/l#SvncHubspotObiects.ohv +6rt/ D HydrakeCrmDataByExternalCallidJob.php +Ere/- HandleHubspotRateLimit.php +44onntlshelCrm/ß CunaOnnartunitineloh.nhnrdAsk anvthina (&4DC° AdantiveView alX Reiect File t8@+ 1 of 7 files →Whe Sorver |l Iearn more / Don't ack adain (todav 10-25)* Reiect allAccent allW Windsurf Teams 1:1 UTF-8 P 4 spaces...
|
PhpStorm
|
faVsco.js – HydrateCrmDataByExternalCallIdJob.php
|
NULL
|
6286
|
|
6287
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Contracts\Services\Crm\SearchTaskInterface;
use Jiminny\Exceptions\CrmUpdateException;
use Jiminny\Exceptions\ModelNotFoundException;
use Jiminny\Integrations\PlaybookResolver;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity\Provider;
use Jiminny\Models\Crm\Field;
use Jiminny\Models;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Repositories\PlaybookCategoryRepository;
use Jiminny\Services\Crm\CrmObjects\CrmObjectsManager;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
use Jiminny\Models\Feature\FeatureEnum;
class HydrateCrmDataByExternalCallIdJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
private LoggerInterface $logger;
private int $activityId;
private Models\Team $team;
private ?Models\Playbook $playbook;
private string $crmCallIdName;
private ?string $activityTypeField;
private SearchTaskInterface $crmService;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(int $activityId)
{
$this->activityId = $activityId;
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
ProviderRegistry $providerRegistry,
ActivityRepository $activityRepository,
LoggerInterface $logger
): void {
$this->logger = $logger;
$activity = $activityRepository->findById($this->activityId);
// find the crm record based on the external call id
try {
$this->validateAndInitiate($activity);
$crmService = $providerRegistry->get($this->getCrmProvider($activity));
if (! $crmService instanceof SearchTaskInterface) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Crm Provider not supported', [
'activityId' => $this->activityId,
'crmProvider' => $crmService->getDisplayName(),
]);
return;
}
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$fields = $this->buildFields();
$filters = $this->buildFilters($activity);
$task = $this->crmService->getTaskByFilterConditions($fields, $filters);
if ($task === null) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Task not found', [
'activityId' => $this->activityId,
'filters' => $filters,
]);
return;
}
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Task found', [
'activityId' => $this->activityId,
'task' => $task,
]);
} catch (CrmUpdateException $exception) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Activity not applicable, skipping', [
'activityId' => $this->activityId,
'callId' => $activity->getTelephonyProviderId(),
'reason' => $exception->getMessage(),
]);
return;
} catch (ModelNotFoundException $exception) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Activity not found', [
'activityId' => $this->activityId,
'reason' => $exception->getMessage(),
]);
return;
}
// save the crm data to the activity
$this->updateActivity($activity, $task);
// optional update crm record with jiminny data
}
/**
* @throws CrmUpdateException
*/
private function validateAndInitiate(?Models\Activity $activity): bool
{
if (! $activity instanceof Models\Activity) {
throw new ModelNotFoundException('Activity not found');
}
if (! $activity->hasUser()) {
throw new CrmUpdateException('No user assigned to activity');
}
$user = $activity->getUser();
$this->team = $user->getTeam();
if (! $this->team->hasFeature(FeatureEnum::SUPPORTS_UPDATE_BY_EXTERNAL_CALL_ID)) {
throw new CrmUpdateException('Feature flag not enabled');
}
$diallerProvider = $this->getDiallerProvider($activity);
if (! $this->isSupportedDiallerProvider($diallerProvider)) {
throw new CrmUpdateException('Dialler provider not supported');
}
if (! $activity->hasTelephonyProviderId()) {
throw new CrmUpdateException('No telephony_provider_id on activity');
}
$this->playbook = $this->getPlaybook($user);
if (! $this->playbook instanceof Models\Playbook) {
throw new CrmUpdateException('User has no playbook');
}
$this->activityTypeField = $this->getActivityTypeField();
if ($this->activityTypeField === null) {
throw new CrmUpdateException('No activity field on playbook');
}
$provider = $this->team->getProvider($diallerProvider);
if (
! $provider instanceof Provider ||
! $provider->hasCapabilities() ||
! isset($provider->getCapabilities()['crmCallId'])
) {
throw new CrmUpdateException('No crmCallId assigned to activity_provider');
}
$this->crmCallIdName = (string) $provider->getCapabilities()['crmCallId'];
return true;
}
private function isSupportedDiallerProvider(string $diallerProvider): bool
{
return $diallerProvider === Provider::PROVIDER_AMAZON_CONNECT;
}
private function getDiallerProvider(Models\Activity $activity): string
{
return $activity->getProvider();
}
private function getCrmProvider(Models\Activity $activity): string
{
return $activity->getCrm()->getProviderName();
}
private function getPlaybook(Models\User $user): ?Models\Playbook
{
$playbookResolver = app(PlaybookResolver::class);
return $playbookResolver->resolvePlaybookByUser($user);
}
private function buildFields(): array
{
$fields = $this->crmService->buildTaskSearchFields();
$fields[] = $this->activityTypeField;
return $fields;
}
private function getActivityTypeField(): ?string
{
$activityField = $this->playbook->getActivityField();
if (! $activityField instanceof Field) {
return null;
}
return $activityField->getCrmProviderId();
}
private function buildFilters(Models\Activity $activity): array
{
return [$this->crmCallIdName => $activity->getTelephonyProviderId()];
}
private function updateActivity($activity, $task): void
{
$activityData = $this->prepareActivityData($activity, $task);
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Updating activity', [
'activityId' => $this->activityId,
'activityData' => $activityData,
]);
$activity->update($activityData);
}
private function prepareActivityData(Models\Activity $activity, array $task): array
{
$activityData['crm_provider_id'] = $activity->crm_provider_id ?? $task['Id'];
if ($task[$this->activityTypeField] && $activity->getPlaybookCategoryId() === null) {
$playbookCategoryRepository = app(PlaybookCategoryRepository::class);
$playbookCategory = $playbookCategoryRepository->getPlaybookCategoryByName(
$this->playbook,
$task[$this->activityTypeField]
);
if ($playbookCategory instanceof Models\PlaybookCategory) {
$activityData['playbook_category_id'] = $playbookCategory->getId();
}
}
if (! $activity->hasProspect()) {
$this->logger->info('[HydrateCrmDataByExternalCallIdJob] Activity has no prospect', [
'activityId' => $this->activityId,
]);
$activityData = $this->attachProspectData($activityData, $task);
}
return $activityData;
}
private function attachProspectData(array $activityData, array $task): array
{
$crmObjects = $this->crmService->mapCrmObjects($task);
$crmObjectManager = app(CrmObjectsManager::class, ['config' => $this->team->getCrmConfiguration()]);
foreach ($crmObjects as $type => $crmId) {
$crmObject = $crmObjectManager->findByCrmProviderId($type, $crmId);
if ($crmObject !== null) {
$activityField = $type . '_id';
$activityData[$activityField] = $crmObject->getId();
}
}
return $activityData;
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – HydrateCrmDataByExternalCallIdJob.php
|
NULL
|
6287
|
|
6288
|
PhostormINavigareCodeFV faVsco.js?9 master kProiec PhostormINavigareCodeFV faVsco.js?9 master kProiect© DetachActivityObject.phpeeeeTephleieerlmre© SyncOpportunitiesJob.phpC) Client.phpDownloadlrack.onc) Importcall.one© ImportExternalActivc) Impont willovideos@ IsActivitvReadyForf © Hubspot/Service.phpC) PayloadBuilder.php(c) CrmActivityService.phoC) CachedCrmServiceDecorator.phoC) MatchermData.phpTHubspot/.SyncCrmEntitiesTrait.pho(C) Pipedrive/Service.php© UpdateCrmFieldDaOpportunitySyncTest.phpc) Updatecustomerm>C JustCall> M PushSummarvToCrm→ RinaCentrall→ZoomPhone(C) ActivitvChandeCatedoC) [EMAIL]© ConferenceCrmMatchC)DeleteActivities.oho© Delete TeamChurnDatac) DoleteTeamcRetention© HardDeleteActivities.p© HardDeleteActivity.ph© MatchMeetingOwner.p(C) PoindeyForAccount.lolli© ReindexForContactJob© ReindexForGroupJob.f© ReindexForLeadJob.pl© ReindexForOpportunit© ReindexForUserJob.ph© RetryActivitySyncJob.© SyncActivity.php© TeardownStream.php> C AiAutomationC AiReports•D AUdIoAutomatedReports© RequestGenerateAskJ© RequestGenerateRenc(C) SendRevort ExoirinaSo@ SendReport.Job.phpC)SendRenortMail.lob.oh@ SendRenortNotGeneral> M CalendarDCrmv M Delete@ DeleteAccount loh.iC) DeleteContact.loh.r@ Noloto CrmEntitvTree Nolotol ond lnh nhr(e) Nelete@pportunityJ(e) VorifvActivitvCrmT.Servicelntertace.phpk?phpdeciare (strict tvnesai):namespace Jiminny \Jobs\Activity \Import;use Illuminate \Bus\Queueableuse Illuminate \Contracts\Queue\ShouldQueue;use eecuminace roundaczon bus uispacchaole.luce Tlluminate Queve interactswithuveueuse Jiminny component yueve constantsuse Jininny contracts services uri searchlaskenterrace.use viminny cxcepcions crnupdarecxceptionuse Jiminny Exceptions Modeunotroundexceptlon:use Jiminny Inteqrations Playbookkesolver.luse Jiminny Jobs Middleware HandleHubspotRateLimit:use Jiminny Models Activity Provider:use Jiminny Models Crm rlelduse Jaminny modelsuse Jiminny Renositories ActivitvRevository:use Jiminny Renositories PlavbookCategorvRepository:useJaminny Services Crm ProviderRegistry:use Psr\Log \LoggerInterfaceuse Jiminny Models Feature Featureenum.class HvdrateCrmDataßvExternalCal1Td.Joh imolements ShouldQueueuse Disnatchableuse InteractsWithQueue;use Queueable:private LoggerInterface $logger:y usagesprivate int SactivityId;private ModelsTeam $team:private ?Models\Playbook $playbook:private string scrmualtbahapesyAccept File &+X Reiect File t8@+ 5 of 7 files →SonarQube for INF cuaadonc. Netect more cecurity iccuec in vour DHP filec II Try S.Whe Sorver |l Iearn more / Don't ack adain (todav 10-25)ACCept Renect© ProviderRateLimiter.php X = custom.logElaravel.log4 SF jiminny@localhost]A HS_local [jiminny@localhost]tiò accounts jiminA console [PROD]# console [eu)A console [STAGING]›use ...class Providerratelimitenorotected RateLimiter SrateLimiter:public function __construct(RateLimiter SrateLimiter)l...hpublic function canMakeRequest(RateLimited $provider): bool/** Ovar RateLimitInterface $rateLimit */foreach (Sprovider->getRateLimits as $rateLimit) ‹$key = SrateLimit->getKeyO;if (Sthis->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {return true:pubLic tunction requestava1lableln kateLimited Sprovider: 1ntreturn Sprovider->getRateLimits@->isNotEmptv@Sproviden->getRateLimits@(RateLimitInterface SrateLimit): int => Sthis->rateLimiter->availableIn(SrateLii->maxonublic function incrementReauestCountRateLimited Soroviden): void** Avan Ratel imitIntenface Sratel imit *^foreach (Sprovider->getRateLimits as $rateLimit) ‹Sthicesnat.mit-›getKey, $rateLimit->getWindow0):lbl100% 12Thu 7 May 21:09:00AskJiminnyReportActivityServiceTest vCascadeHubspot Rate Limiting• Hubspot Rate Limit• ap /Servs/ts/cs/ndar/Yomman8/.mportParticapants.phpprivate functcon te rospeetbataßyeratCt78t8yg 31): 7array+0 ..cp /Srvs/skas//2achey/emg s6, groec-oraifr-pfton natchExactlyByEna 1)/function natchbyDoratnv*pubtic function matchEybontynYstring semag , ant suset gunud : arrayzarray• cd /Users/lukas/jiminny/app && grep -n "function matchByProspectIdentifier196÷|private function matchByProspectIdentifier(Command cd. grep, heado ce dusers/lukas/jiminny/app && grep -n "findByProspectIdentifier" app/Services/Crm/ProspectCache.php |A You've used 91% of your quota. Quota resets May 8, 11:00 AM GMT+3.app/Jobs/Crm/ MatchActivityCrmData.php +6app/Jobs/Activity/ ConferenceCrmMatcherJob.php +6app/Jobs/Crm/D SyncHubsp/NObjects.php +6ort/ HydrateCrmDataBvExternalCallid.Job.ohp +Ere/ HandleHubspotRateLimit.php ÷44onntlshelCrm/ß CunaOnnartunitineloh.nhnrdAsk anvthina (&4DC° AdantiveView al* Reiect allAccent alliW Windsurf Teams 1:1 UTF-8 P 4 spaces...
|
PhpStorm
|
faVsco.js – SyncHubspotObjects.php
|
NULL
|
6288
|
|
6289
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelplahl100% <78Thu 7 May 21:09:01STAGE (ssh)*3DOCKERDEV (-zsh)882APP (-zsh)DOCKER (-zsh)docker_lamp_1docker_lamp_12026-05-07 14:30:06 Running ['artisan'meeting-bot:schedule-bot] ….6S DONEdocker_1amp_1fd/1'2>&1docker_lamp_14sDONEdocker_1amp_11 '/usr/local/bin/php' 'artisan'meeting-bot: schedule-bot > */proc/1/2026-05-07 14:30:13 Running ['artisan'dialers:monitor-activities]1 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:17 Running ['artisan' jiminny:monitor-social-accountSJ3s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > */proc/1/fd/1'2>&1docker_lamp_12sDONEdocker_lamp_12026-05-07 14:30:20 Running ['artisan' mailbox:skip-lists:refresh]1 '/usr/local/bin/php' 'artisan'mailbox: skip-lists:refresh › */proc/1/fd/1'2>&1docker_lamp_112026-05-07 14:30:23 Running ['artisan' mailbox:batch:process --max-batches=15]2s DONEdocker_Lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batches=15 >*/proc/1/fd/1' 2>&1docker_lamp_12026-05-07 14:30:25 Running ['artisan' conference:monitor: count]1S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' conference:monitor: count > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:27 Running ['artisan' activity:purge-stale]2S DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan' activity:purge-stale › '/proc/1/fd/12>&1docker_lamp_1docker_lamp_1docker_lamp_12026-05-07 14:30:30 Running ['artisan' mailbox:text-relay:sync] {"error":"invalid_request""error_description": "Invalidimpersonation \u0026quot; sub\u0026quot;field: @"docker_1amp_11}docker_lamp_1docker_lamp_11 '/usr/local/bin/php' 'artisan' mailbox:text-relay:sync › */proc/1/fd/1' 2>&1docker_1amp_12026-05-07 14:30:35 Running ['artisan'conference:pre-meeting-notification]10s DONEdocker_1amp_111 '/usr/local/bin/php' 'artisan' conference:pre-meeting-notification'/proc/1/fd/1' 2>&1unexpected EOFukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/infrastructure/dev/docker (develop) $-zsh• 84screenpipe*Y2PROD (ssh)New release '24.04.4 LTS' available.Run 'do-release-upgrade' to upgrade to it.-zsh|*** System restart required ***Last login: Mon Apr 27 07:45:27 2026 from 212.5.153.87X L3 EU (-zsh)Last login: Thu May 7 09:29:14 on consolePoetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|T4STAGE (ssh)Run 'do-release-upgrade' to upgrade to it.*** System restart required ***Last login: Tue Apr 28 06:25:10 2026 from 212.5.153.87in:-$XIT5QA (-zsh)Last login: Thu May 7 09:44:56on ttys002Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX 16FE (-zsh)Last login: Thu May 7 09:44:56on ttys004₴6181+PRODSTAGEPoetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I17 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|...
|
PhpStorm
|
faVsco.js – SyncHubspotObjects.php
|
NULL
|
6289
|
|
6290
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
NULL
|
6290
|
|
6291
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
NULL
|
6291
|
|
6292
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
NULL
|
6292
|
|
6293
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
NULL
|
6293
|
|
6294
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
NULL
|
6294
|
|
6295
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
NULL
|
6295
|
|
6296
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
NULL
|
6296
|
|
6297
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
8
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Crm;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Connection;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Exceptions\InvalidArgumentException;
use Jiminny\Jobs\Job;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Crm\Configuration;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmActivityService;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class MatchActivityCrmData extends Job implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue;
use SerializesModels;
public int $tries = 3;
private int $activityId;
private ?Configuration $fromConfiguration;
private bool $remoteSearch;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(
int $activityId,
?Configuration $fromConfiguration = null,
bool $remoteSearch = false,
) {
$this->activityId = $activityId;
$this->fromConfiguration = $fromConfiguration;
$this->remoteSearch = $remoteSearch;
$this->onQueue(Constants::QUEUE_ANALYTICS_LOW);
}
public function uniqueId(): string
{
$configId = $this->fromConfiguration?->getId() ?? 0;
$remote = $this->remoteSearch ? 'remote' : 'local';
return "$this->activityId:$configId:$remote";
}
public function timeout(): int
{
return 300; // 5 minutes max execution time
}
public function uniqueFor(): int
{
return $this->timeout() + 60; // timeout + 1 minute buffer
}
public function backoff(): array
{
return [30, 90, 180];
}
/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws Exception|Throwable
*/
public function handle(
ActivityRepository $activityRepository,
CrmActivityService $crmActivityService,
Connection $connection,
): void {
$activity = $activityRepository->findById($this->activityId);
if ($activity === null) {
throw new InvalidArgumentException('[MatchActivityCrmData] Cannot find activity.');
}
try {
$connection->transaction(function () use ($activity, $crmActivityService, $activityRepository) {
Log::info('[MatchActivityCrmData] Starting CRM data matching', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'set_configuration' => $this->fromConfiguration?->getId(),
'old_state' => [
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
],
]);
$this->resetCrmMappings($activity, $activityRepository);
$this->switchCrmConfigurationIfNeeded($activity);
$activity->refresh();
$crmActivityService->updateCrmData(
activity: $activity,
remoteSearch: $this->remoteSearch,
);
$hasMatch = $activity->getLead() !== null
|| $activity->getContact() !== null
|| $activity->getAccount() !== null
|| $activity->getOpportunity() !== null;
if ($hasMatch) {
Log::info('[MatchActivityCrmData] Successfully matched CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'lead_id' => $activity->getLead()?->getId(),
'contact_id' => $activity->getContact()?->getId(),
'account_id' => $activity->getAccount()?->getId(),
'opportunity_id' => $activity->getOpportunity()?->getId(),
'stage_id' => $activity->getStage()?->getId(),
]);
} else {
Log::info('[MatchActivityCrmData] No CRM match found', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
]);
}
});
} catch (Throwable $e) {
Log::error('[MatchActivityCrmData] Failed to match CRM data', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
public function failed(Throwable $exception): void
{
Log::error('[MatchActivityCrmData] Job permanently failed after all retries', [
'activity' => $this->activityId,
'remote_search' => $this->remoteSearch,
'from_configuration' => $this->fromConfiguration?->getId(),
'exception' => $exception->getMessage(),
'attempts' => $this->attempts(),
]);
}
private function resetCrmMappings(
Activity $activity,
ActivityRepository $activityRepository
): void {
$activity->update([
'lead_id' => null,
'contact_id' => null,
'account_id' => null,
'opportunity_id' => null,
'stage_id' => null,
]);
$participantsOldState = $activityRepository->getActivityParticipants($activity)
->map(function ($participant) {
return [
'id' => $participant->id,
'user_id' => $participant->user_id,
'contact_id' => $participant->contact_id,
'lead_id' => $participant->lead_id,
];
});
if ($participantsOldState->isNotEmpty()) {
Log::info('[MatchActivityCrmData] Participants old state', [
'activity' => $this->activityId,
'participants' => $participantsOldState->toArray(),
]);
}
$activity->participants()->update([
'user_id' => null,
'contact_id' => null,
'lead_id' => null,
]);
}
private function switchCrmConfigurationIfNeeded(Activity $activity): void
{
if ($this->fromConfiguration === null) {
return;
}
if ($activity->getCrm()?->getId() === $this->fromConfiguration->getId()) {
return;
}
Log::info('[MatchActivityCrmData] Switching CRM configuration', [
'activity' => $this->activityId,
'old_configuration' => $activity->getCrm()?->getId(),
'new_configuration' => $this->fromConfiguration->getId(),
]);
$activity->update([
'crm_configuration_id' => $this->fromConfiguration->getId(),
'crm_provider_id' => null,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
NULL
|
6297
|
|
6298
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
iTerm2ShellEditViewSessionScriptsProfilesWindowHelplahl100% <78Thu 7 May 21:11:17STAGE (ssh)*3181DOCKERDEV (-zsh)882APP (-zsh)DOCKER (-zsh)docker_lamp_1docker_lamp_12026-05-07 14:30:06 Running ['artisan'meeting-bot:schedule-bot] ….6S DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan'meeting-bot: schedule-bot > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:13 Running ['artisan'dialers:monitor-activities]4sDONEdocker_1amp_11 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:17 Running ['artisan' jiminny:monitor-social-accountSJ3s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:20 Running ['artisan' mailbox:skip-lists:refresh]2sDONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox: skip-lists:refresh › */proc/1/fd/1'2>&1docker_lamp_112026-05-07 14:30:23 Running ['artisan' mailbox:batch:process --max-batches=15]2s DONEdocker_Lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batches=15 >*/proc/1/fd/1' 2>&1docker_lamp_12026-05-07 14:30:25 Running ['artisan' conference:monitor: count]1S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' conference:monitor: count > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:27 Running ['artisan' activity:purge-stale]2S DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan' activity:purge-stale › '/proc/1/fd/12>&1docker_lamp_1docker_lamp_1docker_lamp_1-2026-05-07 14:30:30 Running ['artisan' mailbox:text-relay:sync] {"error":"invalid_request""error_description": "Invalidimpersonation \u0026quot; sub\u0026quot;field: @"docker_1amp_11}docker_lamp_14sDONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' mailbox:text-relay:sync › */proc/1/fd/1' 2>&1docker_1amp_12026-05-07 14:30:35 Running ['artisan'conference:pre-meeting-notification]10s DONEdocker_1amp_111 '/usr/local/bin/php' 'artisan' conference:pre-meeting-notification'/proc/1/fd/1' 2>&1unexpected EOFukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/infrastructure/dev/docker (develop) $-zsh• 84screenpipe*XIY2PROD (ssh)New release '24.04.4 LTS' available.Run 'do-release-upgrade' to upgrade to it.• *5-zsh|*** System restart required ***Last login: Mon Apr 27 07:45:27 2026 from 212.5.153.87X L3 EU (-zsh)Last login: Thu May 7 09:29:14 on consolePoetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|T4STAGE (ssh)Run 'do-release-upgrade' to upgrade to it.*** System restart required ***Last login: Tue Apr 28 06:25:10 2026 from 212.5.153.87in:-$XIQA (-zsh)Last login: Thu May 7 09:44:56on ttys002Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX 16FE (-zsh)Last login: Thu May 7 09:44:56on ttys004₴6+PRODSTAGEPoetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I17 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|...
|
PhpStorm
|
faVsco.js – MatchActivityCrmData.php
|
NULL
|
6298
|
|
6299
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6299
|
|
6300
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
PhostormINavigareCodeFV faVsco.jsProiect© DetachActivityObject.php©) Acuvilycnangecatego© SyncOpportunitiesJob.php• Cllentonp©Assignownersnip.ong© PaginationConfig.phpC DeleteAcuivities.onpC Delete leamenurnDatac) Delele l eamskerention© HardDeleteActivities.pc) Hubspot/Service.phpC) PayloadBuilder.php(c) CrmActivityService.phoC) CachedCrmServiceDecorator.pho© HardDeleteActivity.phi (* Hubspot/.../SyncCrmEntitiesTrait.php© MatchMeetingOwner.rServicelntertace.phpOpportunitySyncTest.phpC) ReindexForAccountJolC) ReindexForcontactJoiC) ReindexForgroupJob.r© ReindexForLead.Job.olc) Reindex Foreooortunit© ReindexForUser.Job.phC) RetrvActivitvSvnc.lob.@ SvncActivity.php(C) TeardownStream.oho>IM A AutomationIM AiRenorts> D AudioN AutomatedRenorts(C) RequestGenerateAck.1© RequestGenerateRepo© SendReportExpiringSo(C) SendReport loh nhn© SendReportMailJob.phc) senakeooninorcenera> C Calendaraermv 0 Deletec) DeleteAccountJob.c) DeleteContactJob.rw Deletecrmentitvurac) Deletelead.oo.oho© DeleteOpportunity-C) VerifvActivitvCrmirv a Hubsoot> TraitsC) FetchMeraedObiecC) Hubsnot AonUninst@ ImnortAccountRatoN. ImnortRatch.lohTraC) ImnortContactRate( ProcescHubsnotWeC) ProceccinternalWel© ProcessMergedObje DrococeWohhookse) lindataDonlWohha) M Calocforcd(C) Pipedrive/Service.phpkrohodeciare (strict tvnesa1):namesnace liminnv Jlohs Activitv:use Illuminate \Bus\Queueableluce Tlluminate Contracts Oueue Shouldoueue:use eecuminace lyveue enteracrswithuveve.looalnawalavewoattlalnallecalllarelaloaualelainwotttlalntuse Jiminny\Jobs\Middleware\HandleHubspotRateLimituse Jiminny Models Activity:use Jininny nodels user.use Jiminny kepositories Activitykepos1toryuse Jiminny services Lalendar Adapter PersistedcventAttendee:use Jiminny Services Lalendar Command Importrarticipantsuse Jiminny services kesolveleamurmconnectionuse Psr Loq LoqgerIntertace:* This 1ob is disootched after a meetina is finished.* Its purpose is to validate all final participants, and run crm matching for them,* in case an opportunitu was created durina the actual meetina.cllass ConferencecrmMatcher.lob imolements Shou dqueueuse InteractsWithQueue:use Queueable:public int $tries = 3;nublic int Stimeout = 120.public function middleware(): array{...}public function construct(private readonly int $activityId){...}public function handle(Acciv1cykepos1tory sacc1v1tykepos1tory.ResolveTeamCrmConnection $crmResolver.Loogerintertace slogger,): void {$logger->info(' [fonferenceCnnMa v Accept File &a ng Reiecffileesh actiwify+ormedata', ['activity id' => Sthis->activitvdlarQube for INE suadections: Netect more cecurity iccuec in vour DHP filec II Try SonarQube Cloud for free /I Download SnarQuhe Sorver |l Iearn more / Don't ack adain (todav 10-25)Renect=custom.logElaravel.log4 SF jiminny@localhost]A HS_local [jiminny@localhost]A console [PROD]# console [eu)A console [STAGING]tiò accounts jiminuse...class Providerratelimitenorotected RateLimiter SrateLimiter:public function __construct(RateLimiter $rateLimiter)(...}public function canMakeRequest(RateLimited $provider): bool/** Ovar RateLimitInterface $rateLimit */foreach (Sprovider->getRateLimits as $rateLimit) ‹$key = SrateLimit->getKeyO:if (Sthis->rateLimiter->tooManyAttempts($key, SrateLimit->getQuotaO)) {return true:pubLic tunction requestavallableln kateLimited Sprovider: 1ntreturn Sprovider->getRateLimits@->isNotEmptv@Sprovider->getRateLimitso(RateLimitInterface SrateLimit): int => Sthis->rateLimiter->availableIn(SrateLii->maxonublic function incrementReauestCountRateLimited Sorovider)• void** Avan Ratel imitIntenface Sratel imit *^foreach (Sprovider->getRateLimits as $rateLimit) ‹Sthic-snatoimit->getKey, SrateLimit->getWindowO):khl100% 12Thu 7 May 21:11:33U AskJiminnyReportActivityServiceTest -CascadeHubspot Rate LimitingHubspot Rate Limit+0 ..ion matchExact lvBvEmail Ifunction matchBvDomain'56:pubLic function natchtyocelynystring Semat , Rant user gu- rdd 5 atre arrayRead CachedCrmServiceDecorator.nhn #| 50-99|ap /ervs/es/asm/cached(ms ev grbecoraifr-ption matchByProspectIdentifierprivate tunction matchByProspectidentitierThoughtsRead CachedCrmServiceDecorator.nho #|190-249Command cd, grep, neag•hea/users/Lukas/jiminny/app && grep -n "FindByProspectIdentifier' app/Services/Crm/ProspectCache•php |public function findByProspectIdentifier(ThoughtsRead ProsoectCache.oho #155-134Thoughts >Read CachedCrmServiceDecorator.oho #L245-294ThoughtsCommand cd, grep• cd /Users/lukas/jiminny/app && grep -n "function matchExactlyByEmail" app/Services/Crm/Hubspot/Service.phppublic function matchExactlyByEmail(string Semail, ?int SuserId = null): ?arrayA You've used 92% of your quota. Quota resets May 8, 11:00 AM GMT+3View alapp/Jobs/Crm/ MatchActivityCrmData.php +6y/ D ConfehenceCrmMatcherJob.php +6aoo/lobs/Crm/l#SvncHubspotObiects.ohv +6rt/# HydrateCrmDataByExternalCallid.Job.oho +6re/ HandleHubspotRateLimit.php ÷44onntlshelPrm/ß CunaOnnartunitineloh.nhnhe6 ImnortPotch lohTroit nhn* Reiect alliiAccent allAsk anvthina (&4DC° AdantiveW Windsurf Toams 1'1UTF.8io 4 spaces...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6300
|
|
6301
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6301
|
|
6302
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6302
|
|
6303
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6303
|
|
6304
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6304
|
|
6305
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6305
|
|
6306
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6306
|
|
6307
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6307
|
|
6308
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6308
|
|
6309
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6309
|
|
6310
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6310
|
|
6311
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
iTerm2ShellEditViewSessionScriptsProfilesWindowHelplahl100% <78Thu 7 May 21:13:59STAGE (ssh)*3181DOCKERDEV (-zsh)882APP (-zsh)DOCKER (-zsh)docker_lamp_1docker_lamp_12026-05-07 14:30:06 Running ['artisan'meeting-bot:schedule-bot] ….6S DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan'meeting-bot: schedule-bot > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:13 Running ['artisan'dialers:monitor-activities]4sDONEdocker_1amp_11 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:17 Running ['artisan' jiminny:monitor-social-accountSJ3s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:20 Running ['artisan' mailbox:skip-lists:refresh]2sDONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox: skip-lists:refresh › */proc/1/fd/1'2>&1docker_lamp_112026-05-07 14:30:23 Running ['artisan' mailbox:batch:process --max-batches=15]2s DONEdocker_Lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batches=15 >*/proc/1/fd/1' 2>&1docker_lamp_12026-05-07 14:30:25 Running ['artisan' conference:monitor: count]1S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' conference:monitor: count > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:27 Running ['artisan' activity:purge-stale]2S DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan' activity:purge-stale › '/proc/1/fd/12>&1docker_lamp_1docker_lamp_1docker_lamp_1-2026-05-07 14:30:30 Running ['artisan' mailbox:text-relay:sync] {"error":"invalid_request""error_description": "Invalidimpersonation \u0026quot; sub\u0026quot;field: @"docker_1amp_11}docker_lamp_14sDONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' mailbox:text-relay:sync › */proc/1/fd/1' 2>&1docker_1amp_12026-05-07 14:30:35 Running ['artisan'conference:pre-meeting-notification]10s DONEdocker_1amp_111 '/usr/local/bin/php' 'artisan' conference:pre-meeting-notification'/proc/1/fd/1' 2>&1unexpected EOFukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/infrastructure/dev/docker (develop) $-zsh• 84screenpipe*XIY2PROD (ssh)New release '24.04.4 LTS' available.Run 'do-release-upgrade' to upgrade to it.• *5-zsh|*** System restart required ***Last login: Mon Apr 27 07:45:27 2026 from 212.5.153.87X L3 EU (-zsh)Last login: Thu May 7 09:29:14 on consolePoetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|T4STAGE (ssh)Run 'do-release-upgrade' to upgrade to it.*** System restart required ***Last login: Tue Apr 28 06:25:10 2026 from 212.5.153.87in:-$XIQA (-zsh)Last login: Thu May 7 09:44:56on ttys002Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX 16FE (-zsh)Last login: Thu May 7 09:44:56on ttys004₴6PRODSTAGEPoetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I17 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6311
|
|
6312
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6312
|
|
6313
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6313
|
|
6314
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6314
|
|
6315
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
PhostormINavigarecodeProiect v© DetachActivityObject.php© ActivityChangeCatego© SyncOpportunitiesJob.php• Cllentonp©Assignownersnip.ongC DeleteAcuivities.onpC Delete leamenurnDatac) Delele l eamskerention© HardDeleteActivities.pc) Hubspot/Service.phpC) PayloadBuilder.php(c) CrmActivityService.phoC) CachedCrmServiceDecorator.pho© HardDeleteActivity.pnj ( Hubspot/.../SyncCrmEntitiesTrait.php© MatchMeetingOwner.r© Pipedrive/Service.phpServicelntertace.php© OpportunitySyncTest.phpclass ConferencecrmMatcherJob imolements Shou doueueC) ReindexForAccountJoloublic function handledC) ReindexForcontactJoiC) ReindexForgroupJob.r© ReindexForUser.Job.phC) RetrvActivitvSvnc.lob.n@ SvncActivity.php© TeardownStream.php•IM A AutomationIM AiRenorts> D AudioMAutomatedRenorts(C) RequestGenerateAck.lil© RequestGenerateRepo© SendReportExpiringSoC) SendReport.loh nhn© SendReportMailJob.phc senakeponinorcenera• C Calendaraermv 0 Deletec) DeleteAccountJob.c) DeleteContactJob.rw Deletecrmentitvurac) Deletelead.oo.oho© DeleteOpportunityC) VerifvActivitvCrmirv a Hubsoot> TraitsC) FetchMeraedObiecC) Hubsnot AonUninst@ ImnortAccountRatoN. ImnortRatch.lohTraC) ImnortContactRateC) Imnort@nnortunitvf# ProcessHubsnotWeC) ProceccinternalWelG DrococcMernodohie DrococeWohhookse) lindataDonlWohha) M CalocforcdtrySuser = Sactivitv->aetUser0*SpersistedAttendees = $this->collectParticipants(Sactivity):i€ Cemntv(SnensistedAttendeps))₫Slogger->info('[ConferenceCrmMatcherJob] No valid participants to process', [=> $this->activityId,ScrmService = ScrmResolver->resolveForUser(Suser):SimportParticipants = Sthis->createImportParticipants(Sactivity. Suser. SpersistedAttendees):SimportParticipants->setcrmservicescrmservice^SimportParticipants->retreshcrmbataoSloager->info('[ConfernceCrmMatcherJobl Refresh activity erm data finished'. [lactivity id' => sthis-›activitvid.'particivants count' => countSoersistedAttendees).} catch (SocialAccountTokenInvalidException $e) {Sloagen->error('ConferencecrmMatcherJ0b CRM token invalid'.=> Se->aetMessade@i.i// Prevent retries when no social account is available.Sthis->fail(Se):catch Eycontion Col d$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [tactivity idt -s Cthic-sactivitvtdl'error' => $e->qetMessage0.'trace' => $e->qetTraceAsStringO.D:tlof2 edits yAccept File &+X Reiect File t8@+ 6 of 7 files →throw SearQube for INE suadections: Netect more cecurity iccuec in vour DHP filec II Try SonarQube Cloud for fres /I Download oarQuhe Sorver |l Iearn more II Don't ack adain (todav 10-25)© ProviderRateLimiter.php X = custom.logElaravel.log4 SF jiminny@localhost]A HS_local [jiminny@localhost]tiò accounts firA console (PROD]# console [eu)A console [STAGING]use...class Providerratelimitenorotected RateLimiter SrateLimiter:public function __construct(RateLimiter $rateLimiter)(...}public function canMakeRequest(RateLimited Sprovider): bool/** Ovar RateLimitInterface $rateLimit */foreach (Sprovider->getRateLimits as $rateLimit) ‹$key = SrateLimit->getKeyO;if (Sthis->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {return true:pubLic tunction requestavallableln kateLimited Sprovider: 1ntreturn Sprovider->getRateLimits@->isNotEmptv@Sproviden->getRateLimits@(RateLimitInterface SrateLimit): int => Sthis->rateLimiter->availableIn(SrateLir->maxonublic function incrementRequestCountRateLimited Sorovider)• void** Avan Ratel imitIntenface Sratel imit *^foreach (Sprovider->getRateLimits as $rateLimit){Sthicesnasmit-›getKey, $rateLimit->getWindow0):100% 12Thu 7 May 21:14:27U AskJiminnyReportActivityServiceTest vCascadeHubspot Rate Limiting• Hubspot Rate Limit+0 ..• tunet20/ naten/3 ane YTesp, 6f grad =20→hacchbyNanel macchbynanel" -include- *.onp app/grep =vusage: greptl-abeaberemnalotcannoopqkeslacaaxezea hund teb humd tee thumd.Lrectories=actionl (--label] [--line-bufferedl-null] (oatternl (file ...1• cd /Users/lukas/jiminny/app && grep -n "matchByName" app/Services/Crm/CrmActivityService.phpLLI:srecoras = sch1s→>decorator->matchbyName(palcorssonnusnnthrepublic function matchByName(string $name, ?int SuserId = null): ?arrayCommand cd, grep, heado esearchcs/141s/5ervde=ngeta ginateca*grsefme(uremaxcphy app/SobS api/Listeners 2/Sev/ncet | grep -vTest head -20A You've used 93% of your quota. Quota resets May 8, 11:00 AM GMT+3.app/Jobs/Crm/ MatchActivityCrmData.php +6app/Jobs/Activity/ ConferenceCrmMatcherJob.php +6aoo/lobs/Crm/l#SvncHubspotObiects.ohv +6ort/G HydrateCrmDataBvExternalCallid.Job.oho +6re/ HandleHubspotRateLimit.php ÷44onntlshelPrm/ß CunaOnnartunitineloh.nhn,dAsk anvthina (&4DC° AdantiveView al* Reiect alliiAccent alliW Windsurf Teams 1:1 UTF-8 P 4 spaces...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6315
|
|
6316
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6316
|
|
6317
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\User;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Calendar\Adapter\PersistedEventAttendee;
use Jiminny\Services\Calendar\Command\ImportParticipants;
use Jiminny\Services\ResolveTeamCrmConnection;
use Psr\Log\LoggerInterface;
/**
* This job is dispatched after a meeting is finished.
* Its purpose is to validate all final participants, and run crm matching for them,
* in case an opportunity was created during the actual meeting.
*/
class ConferenceCrmMatcherJob implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
public int $tries = 3;
public int $timeout = 120;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(private readonly int $activityId)
{
}
public function handle(
ActivityRepository $activityRepository,
ResolveTeamCrmConnection $crmResolver,
LoggerInterface $logger,
): void {
$logger->info('[ConferenceCrmMatcherJob] Trying to refresh activity crm data', [
'activity_id' => $this->activityId,
]);
$activity = $activityRepository->findById($this->activityId);
if (! $activity) {
$logger->warning('[ConferenceCrmMatcherJob] Activity not found', [
'activity_id' => $this->activityId,
]);
return;
}
try {
$user = $activity->getUser();
$persistedAttendees = $this->collectParticipants($activity);
if (empty($persistedAttendees)) {
$logger->info('[ConferenceCrmMatcherJob] No valid participants to process', [
'activity_id' => $this->activityId,
]);
return;
}
$crmService = $crmResolver->resolveForUser($user);
$importParticipants = $this->createImportParticipants($activity, $user, $persistedAttendees);
$importParticipants->setCrmService($crmService);
$importParticipants->refreshCrmData();
$logger->info('[ConferenceCrmMatcherJob] Refresh activity crm data finished', [
'activity_id' => $this->activityId,
'participants_count' => count($persistedAttendees),
]);
} catch (SocialAccountTokenInvalidException $e) {
$logger->error('[ConferenceCrmMatcherJob] CRM token invalid', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
]);
// Prevent retries when no social account is available.
$this->fail($e);
} catch (\Exception $e) {
$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [
'activity_id' => $this->activityId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw $e;
}
}
private function collectParticipants(Activity $activity): array
{
$persistedAttendees = [];
$participants = $activity->getParticipants();
$activityOwnerId = $activity->getUserId();
foreach ($participants as $participant) {
$persistedAttendee = new PersistedEventAttendee($participant);
$persistedAttendee->setIsOrganizer($participant->getUserId() === $activityOwnerId);
if (! $persistedAttendee->email()) {
continue;
}
$persistedAttendees[] = $persistedAttendee;
}
return $persistedAttendees;
}
private function createImportParticipants(Activity $activity, User $user, array $attendees): ImportParticipants
{
return app(ImportParticipants::class, [
'user' => $user,
'activity' => $activity,
'attendees' => $attendees,
]);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6317
|
|
6318
|
PhostormINavigarecodeProiect v© DetachActivityObje PhostormINavigarecodeProiect v© DetachActivityObject.php© ActivityChangeCatego© SyncOpportunitiesJob.php• Cllentonp©Assignownersnip.ongC DelereAcuivities.onpC Delete leamenurnDatac) Delele l eamskerention© HardDeleteActivities.pc) Hubspot/Service.phpC) PayloadBuilder.php(c) CrmActivityService.phoC) CachedCrmServiceDecorator.pho© HardDeleteActivity.pnj ( Hubspot/.../SyncCrmEntitiesTrait.php© MatchMeetingOwner.r© Pipedrive/Service.phpServicelntertace.php© OpportunitySyncTest.phpclass ConferencecrmMatcherJob imolements Shou doueueC) ReindexForAccountJoloublic function handledC) ReindexForcontactJoiC) ReindexForgroupJob.r© ReindexForUser.Job.phC) RetrvActivitvSvnc.lob.n@ SvncActivity.php© TeardownStream.php•IM A AutomationIM AiRenorts> D AudioMAutomatedRenorts(C) RequestGenerateAck.lil© RequestGenerateRepo© SendReportExpiringSoC) SendReport.loh nhn© SendReportMailJob.phc senakeponinorcenera• C Calendaraermv 0 Deletec) DeleteAccountJob.c) DeleteContactJob.rw Deletecrmentitvurac) Deletelead.oo.oho© DeleteOpportunityC) VerifvActivitvCrmirv a Hubsoot> TraitsC) FetchMeraedObiecC) Hubsnot AonUninst@ ImnortAccountRatoN. ImnortRatch.lohTraC) ImnortContactRateC) Imnort@nnortunitvf# ProcessHubsnotWeC) ProceccinternalWelC DrococcMornedobie DrococeWohhookse) lindataDonlWohha) M CalocforcdtrySuser = Sactivitv->aetUser@:SpersistedAttendees = $this->collectParticipants(Sactivity):i€ Cemntv(SnensistedAttendeps))₫Slogger->info('[ConferenceCrmMatcherJob] No valid participants to process', [=> $this->activityId,ScrmService = ScrmResolver->resolveForUser(Suser):SimportParticipants = Sthis->createImportParticipants(Sactivity. Suser. SpersistedAttendees):SimportParticipants->setcrmservicescrmservicenSimportParticipants->retreshcrmbataoSloager->info('[ConfernceCrmMatcherJobl Refresh activity erm data finished'. [l'activity id' => sthis-›activitvid.'particivants count' => countSoersistedAttendees).} catch (SocialAccountTokenInvalidException $e) {Sloagen->error('ConferencecrmMatcherJ0b CRM token invalid'.=> Se->aetMessade@i.i// Prevent retries when no social account is available.Sthis->fail(Se):catch Eycontion Col d$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [tactivity idt -s Cthic-sactivitutdl'error' => $e->qetMessage0.'trace' => $e->qetTraceAsStringO.D:tlof2 edits yAccept File &+X Reiect File t8@+ 6 of 7 files →throw SearQube for INE suadections: Netect more cecurity iccuec in vour DHP filec II Try SonarQube Cloud for fres /I Download oarQuhe Sorver |l Iearn more /I Don't ack adain (todav 10-25)© ProviderRateLimiter.php X = custom.logElaravel.log4 SF jiminny@localhost]A HS_local [jiminny@localhost]tiò accounts fürA console (PROD]# console [eu)A console [STAGING]use...class Providerratelimitenorotected RateLimiter SrateLimiter:public function __construct(RateLimiter $rateLimiter)(...}public function canMakeRequest(RateLimited $provider): bool/** Ovar RateLimitInterface SrateLimit */foreach (Sprovider->getRateLimits as $rateLimit) ‹$key = SrateLimit->getKeyO:if (Sthis->rateLimiter->tooManyAttempts($key, SrateLimit->getQuotaO)) ‹return true:pubLic tunction requestavallableln kateLimited Sprovider : 1ntreturn Sprovider->getRateLimits(->isNotEmotv@Sprovider->getRateLimitso(RateLimitInterface SrateLimit): int => Sthis->rateLimiter->availableIn(SrateLir->maxonublic function incrementRequestCountRateLimited Sorovider)• void** Avan Ratel imitIntenface Sratel imit *^foreach (Sprovider->getRateLimits as $rateLimit){Sthicesnasmit-›getKey, $rateLimit->getWindow0):TO lo100% 12Thu 7 May 21:15:02U AskJiminnyReportActivityServiceTest vCascadeHubspot Rate Limiting• Hubspot Rate Limit+0 ..ContinueCommand cd, areo, headop /yoer /bxev/nint 1 grep -t Trst Th iC -ActivityServie\|craActavityService" -nc luderi x- phpuSKipwA You've used 93% of your quota. Quota resets May 8, 11:00 AM GMT+3.app/Jobs/Crm/ MatchActivityCrmData.php +6app/Jobs/Activity/ ConferenceCrmMatcherJob.php +6aoo/lobs/Crm/l#SvncHubspotObiects.ohv +6oort/@ HydrateCrmDataByExternalCallid.Job.oho +6app/Jobs/Middleware/ HandleHubspotRateLimit.php +44onntlshelCrm/ß CunaOnnartunitineloh.nhnrdlAsk anvthina (84L)C° AdantiveView al•Reiect alllAccent alli• oW Windsurf Teams 1:1 UTF-8 P 4 spaces...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6318
|
|
6319
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6319
|
|
6320
|
PhostormINavigarecodeProiect v© DetachActivityObje PhostormINavigarecodeProiect v© DetachActivityObject.php© ActivityChangeCatego© SyncOpportunitiesJob.php• Cllentonp©Assignownersnip.ongC DeleteAcuivities.onpC Delete leamenurnDatac) Delele l eamskerention© HardDeleteActivities.pc) Hubspot/Service.phpC) PayloadBuilder.php(c) CrmActivityService.phoC) CachedCrmServiceDecorator.pho© HardDeleteActivity.pnj ( Hubspot/.../SyncCrmEntitiesTrait.php© MatchMeetingOwner.r© Pipedrive/Service.phpServicelntertace.php© OpportunitySyncTest.phpclass ConferencecrmMatcherJob imolements Shou doueueC) ReindexForAccountJoloublic function handledC) ReindexForcontactJoiC) ReindexForgroupJob.r© ReindexForUser.Job.phC) RetrvActivitvSvnc.lob.n@ SvncActivity.php© TeardownStream.php•IM A AutomationIM AiRenorts> D AudioMAutomatedRenorts(C) RequestGenerateAck.lil© RequestGenerateRepo© SendReportExpiringSoC) SendReport.loh nhn© SendReportMailJob.phsenakeponinorcenera• C Calendaraermv 0 Deletec) DeleteAccountJob.c) DeleteContactJob.rw Deletecrmentitvurac) Deletelead.oo.oho© DeleteOpportunityC) VerifvActivitvCrmirv a Hubsoot> TraitsC) FetchMeraedObiecC) Hubsnot AonUninst@ ImnortAccountRatoN. ImnortRatch.lohTraC) ImnortContactRateC) Imnort@nnortunitvf# ProcessHubsnotWeC) ProceccinternalWelG DrococcMernodohie DrococeWohhookse) lindataDonlWohha) M CalocforcdtrySuser = Sactivitv->aetUser0*SpersistedAttendees = $this->collectParticipants(Sactivity):i€ Cemntv(SnensistedAttendeps))₫Slogger->info('[ConferenceCrmMatcherJob] No valid participants to process', [=> $this->activityId,ScrmService = ScrmResolver->resolveForUser(Suser):SimportParticipants = Sthis->createImportParticipants(Sactivity. Suser. SpersistedAttendees):SimportParticipants->setcrmservicescrmservicenSimportParticipants->retreshcrmbataoSloager->info('[ConfernceCrmMatcherJobl Refresh activity erm data finished'. [l'activity id' => sthis-›activitvid.'particivants count' => countSoersistedAttendees).} catch (SocialAccountTokenInvalidException $e) {Sloagen->error('ConferencecrmMatcherJ0b CRM token invalid'.=> Se->aetMessade@i.i// Prevent retries when no social account is available.Sthis->fail(Se):catch Eycontion Col d$logger->error('[ConferenceCrmMatcherJob] Failed to refresh activity crm data', [tactivity idt -s Cthic-sactivitutdl'error' => $e->qetMessage0.'trace' => $e->qetTraceAsStringO.D:tlof2 edits yAccept File &+X Reiect File t8@+ 6 of 7 files →throw Se:arQube for INE suadections: Netect more cecurity iccuec in vour DHP filec II Try SonarQube Cloud for fres /I Download oarQuhe Sorver |l Iearn more /I Don't ack adain (todav 10-25)© ProviderRateLimiter.php X = custom.logElaravel.log4 SF jiminny@localhost]A HS_local jiminny@localhost]tiò accounts fürA console (PROD]# console [eu)A console [STAGING]use...class Providerratelimitenorotected RateLimiter SrateLimiter:public function __construct(RateLimiter $rateLimiter)(...}public function canMakeRequest(RateLimited Sprovider): bool/** Ovar RateLimitInterface $rateLimit */foreach (Sprovider->getRateLimits as $rateLimit) ‹$key = SrateLimit->getKeyO;if (Sthis->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {return true:pubLic tunction requestavallableln kateLimited Sprovider : 1ntreturn Sprovider->getRateLimits@->isNotEmptv@Sproviden->getRateLimits@(RateLimitInterface SrateLimit): int => Sthis->rateLimiter->availableIn(SrateLir->maxonublic function incrementRequestCountRateLimited Sorovider)• void** Avan Ratel imitIntenface Sratel imit *^foreach (Sprovider->getRateLimits as $rateLimit){Sthicesnasmit-›getKey, $rateLimit->getWindow0):hel100% 12Thu 7 May 21:15:12U AskJiminnyReportActivityServiceTest vCascadeHubspot Rate Limiting• Hubspot Rate Limit+0 ..Continue0 /90es/ukas/ntal Y grep st grst Thd -2ctavityServicel/craActAvityService" =nelude "*, php»app/Jobs/Crm/MatchActivityCrmData.php:21:use Jiminny\Services\Crm\CrmActivityService;stonnection-s eransactionScrmActivitvServicessuodateCrmDatalCommand cd, grep, heado 2a/tesens/ukas/n-v Y/st | gr°-3-n "dispatch. *new.JOb \/dispatch(new" -includer"*, php" app/ListenersA You've used 93% of your quota. Quota resets May 8, 11:00 AM GMT+3.app/Jobs/Crm/ MatchActivityCrmData.php +6app/Jobs/Activity/ ConferenceCrmMatcherJob.php +6aoo/lobs/Crm/l#SvncHubspotObiects.ohv +6ort/G HydrateCrmDataBvExternalCallid.Job.oho +6re/ HandleHubspotRateLimit.php ÷44onntlshelCrm/ß CunaOnnartunitineloh.nhnrdAsk anvthina (&4DC° Adantive* Reiect alliiAccent alliW Windsurf Teams 1:1 UTF-8 P 4 spaces...
|
PhpStorm
|
faVsco.js – ConferenceCrmMatcherJob.php
|
NULL
|
6320
|
|
6321
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\DTO\ImportCall\Call;
use Jiminny\Component\TranscriptionSummary\Events\TranscriptionAiSummaryReadyEvent;
use Jiminny\Events\Activities\AiAutomation\ActivityProspectAdded;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Participant;
use Jiminny\Models\Team;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmObjectsResolver;
use Jiminny\Services\Crm\ProspectSearchStrategyFactory;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
class MatchCrmData implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
// AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.
private const int MAX_DELAY = 43140;
// Infrastructure allows max 3 retries (1 initial execution + 3 retries)
public int $tries = 4;
private Call $call;
private int $activityId;
private Team $team;
private ServiceInterface $crmService;
private LoggerInterface $logger;
private array $logContext;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(Call $call, int $activityId)
{
$this->call = $call;
$this->activityId = $activityId;
$this->logContext = [
'activity_id' => $activityId,
'call_id' => $call->getCallId(),
'provider' => $call->getProvider(),
];
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
CrmObjectsResolver $crmObjectsResolver,
ProviderRegistry $providerRegistry,
ProviderRateLimiter $rateLimiter,
ActivityRepository $activityRepository,
LoggerInterface $logger,
Dispatcher $eventDispatcher,
): void {
$this->logger = $logger;
// Activity is already augmented with CRM data, no need to perform the same operation
if ($this->call->isActivityUpdatedWithCrm()) {
$this->logMessage('Skipping activity. Already updated with CRM data');
return;
}
/** @var Activity $activity */
$activity = $activityRepository->findById($this->activityId);
try {
$this->initialiseCrmService($activity, $providerRegistry);
} catch (SocialAccountTokenInvalidException $exception) {
$this->logMessage('Invalid token, retrying');
$this->release(self::MAX_DELAY); // Try again tomorrow
return;
}
$prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);
if ($prospectSearchStrategy->ignoreCrmMatchData()) {
// Ignore any associated opportunity
$this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [
'activity_id' => $this->activityId,
'strategy' => get_class($prospectSearchStrategy),
]);
return;
}
if (! $rateLimiter->canMakeRequest($activity->getCrm())) {
$this->logMessage('Rate limit reached, retrying');
$this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));
return;
}
$this->logMessage('Resolving CRM objects');
$rateLimiter->incrementRequestCount($activity->getCrm());
$crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);
if (empty($crmObjects)) {
$this->logMessage('Could not resolve CRM objects, retrying');
$this->release(3600);
return;
}
[$lead, $account, $opportunity, $contact, $stage] = $crmObjects;
$activity->update([
'lead_id' => $lead->id ?? null,
'contact_id' => $contact->id ?? null,
'account_id' => $account->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'stage_id' => $stage->id ?? null,
]);
$activity->refresh();
$eventDispatcher->dispatch(new ActivityProspectAdded(
activity: $activity,
eventSource: 'match-crm-data'
));
if ($activity->getProspectName() !== null) {
$activity->setTitleFromCallData($this->call);
/** @var Participant $prospectParticipant */
$prospectParticipant = $activity
->participants()
->where(function (Builder $query) use ($activity) {
$query
->whereNull('user_id')
->orWhere('user_id', '!=', $activity->getUserId())
;
})
->first()
;
$activity->updateParticipantCrmData($crmObjects, $prospectParticipant);
}
$this->logMessage('Activity updated');
$this->triggerSummaryPushIfReady($activity, $eventDispatcher);
}
private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void
{
if (! $activity->hasTranscriptionId()) {
return;
}
if ($activity->hasProspectActivitySummaryLog()) {
$this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');
return;
}
$this->logMessage('Triggering summary push after CRM matching');
$eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));
}
private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void
{
$this->team = $activity->getUser()->getTeam();
$crmProviderName = $this->team->getCrmConfiguration()->getProviderName();
$crmService = $providerRegistry->get($crmProviderName);
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$this->logContext['team'] = $this->team->getSlug();
$this->logContext['team_id'] = $this->team->getId();
}
private function logMessage(string $message): void
{
$this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchCrmData.php
|
NULL
|
6321
|
|
6322
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\DTO\ImportCall\Call;
use Jiminny\Component\TranscriptionSummary\Events\TranscriptionAiSummaryReadyEvent;
use Jiminny\Events\Activities\AiAutomation\ActivityProspectAdded;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Participant;
use Jiminny\Models\Team;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmObjectsResolver;
use Jiminny\Services\Crm\ProspectSearchStrategyFactory;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
class MatchCrmData implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
// AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.
private const int MAX_DELAY = 43140;
// Infrastructure allows max 3 retries (1 initial execution + 3 retries)
public int $tries = 4;
private Call $call;
private int $activityId;
private Team $team;
private ServiceInterface $crmService;
private LoggerInterface $logger;
private array $logContext;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(Call $call, int $activityId)
{
$this->call = $call;
$this->activityId = $activityId;
$this->logContext = [
'activity_id' => $activityId,
'call_id' => $call->getCallId(),
'provider' => $call->getProvider(),
];
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
CrmObjectsResolver $crmObjectsResolver,
ProviderRegistry $providerRegistry,
ProviderRateLimiter $rateLimiter,
ActivityRepository $activityRepository,
LoggerInterface $logger,
Dispatcher $eventDispatcher,
): void {
$this->logger = $logger;
// Activity is already augmented with CRM data, no need to perform the same operation
if ($this->call->isActivityUpdatedWithCrm()) {
$this->logMessage('Skipping activity. Already updated with CRM data');
return;
}
/** @var Activity $activity */
$activity = $activityRepository->findById($this->activityId);
try {
$this->initialiseCrmService($activity, $providerRegistry);
} catch (SocialAccountTokenInvalidException $exception) {
$this->logMessage('Invalid token, retrying');
$this->release(self::MAX_DELAY); // Try again tomorrow
return;
}
$prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);
if ($prospectSearchStrategy->ignoreCrmMatchData()) {
// Ignore any associated opportunity
$this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [
'activity_id' => $this->activityId,
'strategy' => get_class($prospectSearchStrategy),
]);
return;
}
if (! $rateLimiter->canMakeRequest($activity->getCrm())) {
$this->logMessage('Rate limit reached, retrying');
$this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));
return;
}
$this->logMessage('Resolving CRM objects');
$rateLimiter->incrementRequestCount($activity->getCrm());
$crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);
if (empty($crmObjects)) {
$this->logMessage('Could not resolve CRM objects, retrying');
$this->release(3600);
return;
}
[$lead, $account, $opportunity, $contact, $stage] = $crmObjects;
$activity->update([
'lead_id' => $lead->id ?? null,
'contact_id' => $contact->id ?? null,
'account_id' => $account->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'stage_id' => $stage->id ?? null,
]);
$activity->refresh();
$eventDispatcher->dispatch(new ActivityProspectAdded(
activity: $activity,
eventSource: 'match-crm-data'
));
if ($activity->getProspectName() !== null) {
$activity->setTitleFromCallData($this->call);
/** @var Participant $prospectParticipant */
$prospectParticipant = $activity
->participants()
->where(function (Builder $query) use ($activity) {
$query
->whereNull('user_id')
->orWhere('user_id', '!=', $activity->getUserId())
;
})
->first()
;
$activity->updateParticipantCrmData($crmObjects, $prospectParticipant);
}
$this->logMessage('Activity updated');
$this->triggerSummaryPushIfReady($activity, $eventDispatcher);
}
private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void
{
if (! $activity->hasTranscriptionId()) {
return;
}
if ($activity->hasProspectActivitySummaryLog()) {
$this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');
return;
}
$this->logMessage('Triggering summary push after CRM matching');
$eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));
}
private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void
{
$this->team = $activity->getUser()->getTeam();
$crmProviderName = $this->team->getCrmConfiguration()->getProviderName();
$crmService = $providerRegistry->get($crmProviderName);
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$this->logContext['team'] = $this->team->getSlug();
$this->logContext['team_id'] = $this->team->getId();
}
private function logMessage(string $message): void
{
$this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchCrmData.php
|
NULL
|
6322
|
|
6323
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes...
|
PhpStorm
|
faVsco.js – MatchCrmData.php
|
NULL
|
6323
|
|
6324
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
iTerm2ShellEditViewSessionScriptsProfilesWindowHelplahl100% <78Thu 7 May 21:15:49STAGE (ssh)*3181DOCKERDEV (-zsh)882APP (-zsh)DOCKER (-zsh)docker_lamp_1docker_lamp_12026-05-07 14:30:06 Running ['artisan'meeting-bot:schedule-bot] ….6S DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan'meeting-bot: schedule-bot > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:13 Running ['artisan'dialers:monitor-activities]4sDONEdocker_1amp_11 '/usr/local/bin/php' 'artisan' dialers:monitor-activities › */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:17 Running ['artisan' jiminny:monitor-social-accountSJ3s DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' jiminny:monitor-social-accounts > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:20 Running ['artisan' mailbox:skip-lists:refresh]2sDONEdocker_lamp_11 '/usr/local/bin/php' 'artisan'mailbox: skip-lists:refresh › */proc/1/fd/1'2>&1docker_lamp_112026-05-07 14:30:23 Running ['artisan' mailbox:batch:process --max-batches=15]2s DONEdocker_Lamp_11 '/usr/local/bin/php' 'artisan'mailbox:batch:process --max-batches=15 >*/proc/1/fd/1' 2>&1docker_lamp_12026-05-07 14:30:25 Running ['artisan' conference:monitor: count]1S DONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' conference:monitor: count > */proc/1/fd/1'2>&1docker_lamp_12026-05-07 14:30:27 Running ['artisan' activity:purge-stale]2S DONEdocker_1amp_11 '/usr/local/bin/php' 'artisan' activity:purge-stale › '/proc/1/fd/12>&1docker_lamp_1docker_lamp_1docker_lamp_1-2026-05-07 14:30:30 Running ['artisan' mailbox:text-relay:sync] {"error":"invalid_request""error_description": "Invalidimpersonation \u0026quot; sub\u0026quot;field: @"docker_1amp_11}docker_lamp_14sDONEdocker_lamp_11 '/usr/local/bin/php' 'artisan' mailbox:text-relay:sync › */proc/1/fd/1' 2>&1docker_1amp_12026-05-07 14:30:35 Running ['artisan'conference:pre-meeting-notification]10s DONEdocker_1amp_111 '/usr/local/bin/php' 'artisan' conference:pre-meeting-notification'/proc/1/fd/1' 2>&1unexpected EOFukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/infrastructure/dev/docker (develop) $-zsh• 84screenpipe*XIY2PROD (ssh)New release '24.04.4 LTS' available.Run 'do-release-upgrade' to upgrade to it.• *5-zsh|*** System restart required ***Last login: Mon Apr 27 07:45:27 2026 from 212.5.153.87X L3 EU (-zsh)Last login: Thu May 7 09:29:14 on consolePoetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.toml file in /Users/lukas or its parents@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|T4STAGE (ssh)Run 'do-release-upgrade' to upgrade to it.*** System restart required ***Last login: Tue Apr 28 06:25:10 2026 from 212.5.153.87in:-$XIQA (-zsh)Last login: Thu May 7 09:44:56on ttys002Poetry could not find a pyproject.toml file in /Users/lukas or its parentsPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentsX 16FE (-zsh)Last login: Thu May 7 09:44:56on ttys004₴6+PRODSTAGEPoetry could not find a pyproject.toml file in /Users/lukas or its parentsFRONTENDPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I17 EXT (-zsh)Poetry could not find a pyproject.toml file in /Users/lukas or its parentsEXTENSIONPoetry could not find a pyproject.tomlfile in /Users/lukas or its parentslukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~ $ I|...
|
PhpStorm
|
faVsco.js – MatchCrmData.php
|
NULL
|
6324
|
|
6325
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\DTO\ImportCall\Call;
use Jiminny\Component\TranscriptionSummary\Events\TranscriptionAiSummaryReadyEvent;
use Jiminny\Events\Activities\AiAutomation\ActivityProspectAdded;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Participant;
use Jiminny\Models\Team;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmObjectsResolver;
use Jiminny\Services\Crm\ProspectSearchStrategyFactory;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
class MatchCrmData implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
// AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.
private const int MAX_DELAY = 43140;
// Infrastructure allows max 3 retries (1 initial execution + 3 retries)
public int $tries = 4;
private Call $call;
private int $activityId;
private Team $team;
private ServiceInterface $crmService;
private LoggerInterface $logger;
private array $logContext;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(Call $call, int $activityId)
{
$this->call = $call;
$this->activityId = $activityId;
$this->logContext = [
'activity_id' => $activityId,
'call_id' => $call->getCallId(),
'provider' => $call->getProvider(),
];
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
CrmObjectsResolver $crmObjectsResolver,
ProviderRegistry $providerRegistry,
ProviderRateLimiter $rateLimiter,
ActivityRepository $activityRepository,
LoggerInterface $logger,
Dispatcher $eventDispatcher,
): void {
$this->logger = $logger;
// Activity is already augmented with CRM data, no need to perform the same operation
if ($this->call->isActivityUpdatedWithCrm()) {
$this->logMessage('Skipping activity. Already updated with CRM data');
return;
}
/** @var Activity $activity */
$activity = $activityRepository->findById($this->activityId);
try {
$this->initialiseCrmService($activity, $providerRegistry);
} catch (SocialAccountTokenInvalidException $exception) {
$this->logMessage('Invalid token, retrying');
$this->release(self::MAX_DELAY); // Try again tomorrow
return;
}
$prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);
if ($prospectSearchStrategy->ignoreCrmMatchData()) {
// Ignore any associated opportunity
$this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [
'activity_id' => $this->activityId,
'strategy' => get_class($prospectSearchStrategy),
]);
return;
}
if (! $rateLimiter->canMakeRequest($activity->getCrm())) {
$this->logMessage('Rate limit reached, retrying');
$this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));
return;
}
$this->logMessage('Resolving CRM objects');
$rateLimiter->incrementRequestCount($activity->getCrm());
$crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);
if (empty($crmObjects)) {
$this->logMessage('Could not resolve CRM objects, retrying');
$this->release(3600);
return;
}
[$lead, $account, $opportunity, $contact, $stage] = $crmObjects;
$activity->update([
'lead_id' => $lead->id ?? null,
'contact_id' => $contact->id ?? null,
'account_id' => $account->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'stage_id' => $stage->id ?? null,
]);
$activity->refresh();
$eventDispatcher->dispatch(new ActivityProspectAdded(
activity: $activity,
eventSource: 'match-crm-data'
));
if ($activity->getProspectName() !== null) {
$activity->setTitleFromCallData($this->call);
/** @var Participant $prospectParticipant */
$prospectParticipant = $activity
->participants()
->where(function (Builder $query) use ($activity) {
$query
->whereNull('user_id')
->orWhere('user_id', '!=', $activity->getUserId())
;
})
->first()
;
$activity->updateParticipantCrmData($crmObjects, $prospectParticipant);
}
$this->logMessage('Activity updated');
$this->triggerSummaryPushIfReady($activity, $eventDispatcher);
}
private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void
{
if (! $activity->hasTranscriptionId()) {
return;
}
if ($activity->hasProspectActivitySummaryLog()) {
$this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');
return;
}
$this->logMessage('Triggering summary push after CRM matching');
$eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));
}
private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void
{
$this->team = $activity->getUser()->getTeam();
$crmProviderName = $this->team->getCrmConfiguration()->getProviderName();
$crmService = $providerRegistry->get($crmProviderName);
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$this->logContext['team'] = $this->team->getSlug();
$this->logContext['team_id'] = $this->team->getId();
}
private function logMessage(string $message): void
{
$this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchCrmData.php
|
NULL
|
6325
|
|
6326
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\DTO\ImportCall\Call;
use Jiminny\Component\TranscriptionSummary\Events\TranscriptionAiSummaryReadyEvent;
use Jiminny\Events\Activities\AiAutomation\ActivityProspectAdded;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Participant;
use Jiminny\Models\Team;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmObjectsResolver;
use Jiminny\Services\Crm\ProspectSearchStrategyFactory;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
class MatchCrmData implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
// AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.
private const int MAX_DELAY = 43140;
// Infrastructure allows max 3 retries (1 initial execution + 3 retries)
public int $tries = 4;
private Call $call;
private int $activityId;
private Team $team;
private ServiceInterface $crmService;
private LoggerInterface $logger;
private array $logContext;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(Call $call, int $activityId)
{
$this->call = $call;
$this->activityId = $activityId;
$this->logContext = [
'activity_id' => $activityId,
'call_id' => $call->getCallId(),
'provider' => $call->getProvider(),
];
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
CrmObjectsResolver $crmObjectsResolver,
ProviderRegistry $providerRegistry,
ProviderRateLimiter $rateLimiter,
ActivityRepository $activityRepository,
LoggerInterface $logger,
Dispatcher $eventDispatcher,
): void {
$this->logger = $logger;
// Activity is already augmented with CRM data, no need to perform the same operation
if ($this->call->isActivityUpdatedWithCrm()) {
$this->logMessage('Skipping activity. Already updated with CRM data');
return;
}
/** @var Activity $activity */
$activity = $activityRepository->findById($this->activityId);
try {
$this->initialiseCrmService($activity, $providerRegistry);
} catch (SocialAccountTokenInvalidException $exception) {
$this->logMessage('Invalid token, retrying');
$this->release(self::MAX_DELAY); // Try again tomorrow
return;
}
$prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);
if ($prospectSearchStrategy->ignoreCrmMatchData()) {
// Ignore any associated opportunity
$this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [
'activity_id' => $this->activityId,
'strategy' => get_class($prospectSearchStrategy),
]);
return;
}
if (! $rateLimiter->canMakeRequest($activity->getCrm())) {
$this->logMessage('Rate limit reached, retrying');
$this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));
return;
}
$this->logMessage('Resolving CRM objects');
$rateLimiter->incrementRequestCount($activity->getCrm());
$crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);
if (empty($crmObjects)) {
$this->logMessage('Could not resolve CRM objects, retrying');
$this->release(3600);
return;
}
[$lead, $account, $opportunity, $contact, $stage] = $crmObjects;
$activity->update([
'lead_id' => $lead->id ?? null,
'contact_id' => $contact->id ?? null,
'account_id' => $account->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'stage_id' => $stage->id ?? null,
]);
$activity->refresh();
$eventDispatcher->dispatch(new ActivityProspectAdded(
activity: $activity,
eventSource: 'match-crm-data'
));
if ($activity->getProspectName() !== null) {
$activity->setTitleFromCallData($this->call);
/** @var Participant $prospectParticipant */
$prospectParticipant = $activity
->participants()
->where(function (Builder $query) use ($activity) {
$query
->whereNull('user_id')
->orWhere('user_id', '!=', $activity->getUserId())
;
})
->first()
;
$activity->updateParticipantCrmData($crmObjects, $prospectParticipant);
}
$this->logMessage('Activity updated');
$this->triggerSummaryPushIfReady($activity, $eventDispatcher);
}
private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void
{
if (! $activity->hasTranscriptionId()) {
return;
}
if ($activity->hasProspectActivitySummaryLog()) {
$this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');
return;
}
$this->logMessage('Triggering summary push after CRM matching');
$eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));
}
private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void
{
$this->team = $activity->getUser()->getTeam();
$crmProviderName = $this->team->getCrmConfiguration()->getProviderName();
$crmService = $providerRegistry->get($crmProviderName);
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$this->logContext['team'] = $this->team->getSlug();
$this->logContext['team_id'] = $this->team->getId();
}
private function logMessage(string $message): void
{
$this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchCrmData.php
|
NULL
|
6326
|
|
6327
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}...
|
PhpStorm
|
faVsco.js – MatchCrmData.php
|
NULL
|
6327
|
|
6328
|
Project: faVsco.js, menu
master, menu
Start Listen Project: faVsco.js, menu
master, menu
Start Listening for PHP Debug Connections
AskJiminnyReportActivityServiceTest
Run 'AskJiminnyReportActivityServiceTest'
Debug 'AskJiminnyReportActivityServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Component\Utility\Service;
use Illuminate\Cache\RateLimiter;
use Jiminny\Contracts\Http\RateLimited;
use Jiminny\Contracts\Http\RateLimitInterface;
class ProviderRateLimiter
{
protected RateLimiter $rateLimiter;
public function __construct(RateLimiter $rateLimiter)
{
$this->rateLimiter = $rateLimiter;
}
public function canMakeRequest(RateLimited $provider): bool
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$key = $rateLimit->getKey();
if ($this->rateLimiter->tooManyAttempts($key, $rateLimit->getQuota())) {
return false;
}
}
return true;
}
public function requestAvailableIn(RateLimited $provider): int
{
return $provider->getRateLimits()->isNotEmpty()
? $provider->getRateLimits()
->map(fn (RateLimitInterface $rateLimit): int => $this->rateLimiter->availableIn($rateLimit->getKey()))
->max()
: 0
;
}
public function incrementRequestCount(RateLimited $provider): void
{
/** @var RateLimitInterface $rateLimit */
foreach ($provider->getRateLimits() as $rateLimit) {
$this->rateLimiter->hit($rateLimit->getKey(), $rateLimit->getWindow());
}
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
<?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\DTO\ImportCall\Call;
use Jiminny\Component\TranscriptionSummary\Events\TranscriptionAiSummaryReadyEvent;
use Jiminny\Events\Activities\AiAutomation\ActivityProspectAdded;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Jobs\Middleware\HandleHubspotRateLimit;
use Jiminny\Models\Activity;
use Jiminny\Models\Participant;
use Jiminny\Models\Team;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmObjectsResolver;
use Jiminny\Services\Crm\ProspectSearchStrategyFactory;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
class MatchCrmData implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
// AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.
private const int MAX_DELAY = 43140;
// Infrastructure allows max 3 retries (1 initial execution + 3 retries)
public int $tries = 4;
private Call $call;
private int $activityId;
private Team $team;
private ServiceInterface $crmService;
private LoggerInterface $logger;
private array $logContext;
public function middleware(): array
{
return [new HandleHubspotRateLimit()];
}
public function __construct(Call $call, int $activityId)
{
$this->call = $call;
$this->activityId = $activityId;
$this->logContext = [
'activity_id' => $activityId,
'call_id' => $call->getCallId(),
'provider' => $call->getProvider(),
];
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
CrmObjectsResolver $crmObjectsResolver,
ProviderRegistry $providerRegistry,
ProviderRateLimiter $rateLimiter,
ActivityRepository $activityRepository,
LoggerInterface $logger,
Dispatcher $eventDispatcher,
): void {
$this->logger = $logger;
// Activity is already augmented with CRM data, no need to perform the same operation
if ($this->call->isActivityUpdatedWithCrm()) {
$this->logMessage('Skipping activity. Already updated with CRM data');
return;
}
/** @var Activity $activity */
$activity = $activityRepository->findById($this->activityId);
try {
$this->initialiseCrmService($activity, $providerRegistry);
} catch (SocialAccountTokenInvalidException $exception) {
$this->logMessage('Invalid token, retrying');
$this->release(self::MAX_DELAY); // Try again tomorrow
return;
}
$prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);
if ($prospectSearchStrategy->ignoreCrmMatchData()) {
// Ignore any associated opportunity
$this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [
'activity_id' => $this->activityId,
'strategy' => get_class($prospectSearchStrategy),
]);
return;
}
if (! $rateLimiter->canMakeRequest($activity->getCrm())) {
$this->logMessage('Rate limit reached, retrying');
$this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));
return;
}
$this->logMessage('Resolving CRM objects');
$rateLimiter->incrementRequestCount($activity->getCrm());
$crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);
if (empty($crmObjects)) {
$this->logMessage('Could not resolve CRM objects, retrying');
$this->release(3600);
return;
}
[$lead, $account, $opportunity, $contact, $stage] = $crmObjects;
$activity->update([
'lead_id' => $lead->id ?? null,
'contact_id' => $contact->id ?? null,
'account_id' => $account->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'stage_id' => $stage->id ?? null,
]);
$activity->refresh();
$eventDispatcher->dispatch(new ActivityProspectAdded(
activity: $activity,
eventSource: 'match-crm-data'
));
if ($activity->getProspectName() !== null) {
$activity->setTitleFromCallData($this->call);
/** @var Participant $prospectParticipant */
$prospectParticipant = $activity
->participants()
->where(function (Builder $query) use ($activity) {
$query
->whereNull('user_id')
->orWhere('user_id', '!=', $activity->getUserId())
;
})
->first()
;
$activity->updateParticipantCrmData($crmObjects, $prospectParticipant);
}
$this->logMessage('Activity updated');
$this->triggerSummaryPushIfReady($activity, $eventDispatcher);
}
private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void
{
if (! $activity->hasTranscriptionId()) {
return;
}
if ($activity->hasProspectActivitySummaryLog()) {
$this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');
return;
}
$this->logMessage('Triggering summary push after CRM matching');
$eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));
}
private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void
{
$this->team = $activity->getUser()->getTeam();
$crmProviderName = $this->team->getCrmConfiguration()->getProviderName();
$crmService = $providerRegistry->get($crmProviderName);
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$this->logContext['team'] = $this->team->getSlug();
$this->logContext['team_id'] = $this->team->getId();
}
private function logMessage(string $message): void
{
$this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);
}
}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
PhpStorm
|
faVsco.js – MatchCrmData.php
|
NULL
|
6328
|